/* * Copyright 2004-2015 the Seasar Foundation and the Others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ package org.seasar.framework.jpa.impl; import java.io.File; import java.net.URL; import java.util.List; import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; import javax.persistence.spi.ClassTransformer; import javax.persistence.spi.PersistenceUnitInfo; import org.seasar.framework.jpa.PersistenceClassTransformer; import org.seasar.framework.jpa.util.ChildFirstClassLoader; import org.seasar.framework.jpa.util.ClassLoaderEvent; import org.seasar.framework.jpa.util.ClassLoaderListener; import org.seasar.framework.jpa.util.ClassTransformerUtil; import org.seasar.framework.util.ClassLoaderUtil; import org.seasar.framework.util.ClassUtil; import org.seasar.framework.util.FileUtil; import org.seasar.framework.util.JarFileUtil; import org.seasar.framework.util.URLUtil; import org.seasar.framework.util.tiger.CollectionsUtil; import static org.seasar.framework.util.tiger.IterableAdapter.*; /** * 永続クラスをトランスフォームします。 * * @author taedium */ public class PersistenceClassTransformerImpl implements PersistenceClassTransformer { /** トランスフォームした永続クラスをロードする対象から除外するクラスローダのクラス名 */ protected Set<String> ignoreLoaderClassNames = CollectionsUtil.newHashSet(); /** * インスタンスを構築します。 * */ public PersistenceClassTransformerImpl() { } /** * トランスフォームした永続クラスをロードする対象から除外するクラスローダのクラス名を追加します。 * * @param ignoreLoaderClassName * トランスフォームした永続クラスをロードする対象から除外するクラスローダのクラス名 */ public void addIgnoreLoaderClassName(final String ignoreLoaderClassName) { ignoreLoaderClassNames.add(ignoreLoaderClassName); } public void transform(final PersistenceUnitInfo unitInfo) { final List<ClassTransformer> transformers = PersistenceUnitInfoImpl.class .cast(unitInfo).getTransformers(); final ClassLoader classLoader = unitInfo.getClassLoader(); final ClassLoader targetLoader = getTargetClassLoader(classLoader); final ChildFirstClassLoader tempLoader = new ChildFirstClassLoader( targetLoader); tempLoader.addClassLoaderListener(new ClassLoaderListener() { public void classFinded(final ClassLoaderEvent event) { final String className = event.getClassName(); byte[] bytes = event.getBytecode(); for (final ClassTransformer transformer : transformers) { bytes = transform(transformer, targetLoader, className, bytes); } ClassLoaderUtil.defineClass(targetLoader, className, bytes, 0, bytes.length); } }); loadPersistenceClasses(unitInfo, tempLoader); } public Class<?> transform(final PersistenceUnitInfo unitInfo, final String className, final byte[] bytecode) { final List<ClassTransformer> transformers = PersistenceUnitInfoImpl.class .cast(unitInfo).getTransformers(); final ClassLoader classLoader = unitInfo.getClassLoader(); final ClassLoader targetLoader = getTargetClassLoader(classLoader); byte[] bytes = bytecode; for (final ClassTransformer transformer : transformers) { bytes = transform(transformer, targetLoader, className, bytes); } return ClassLoaderUtil.defineClass(targetLoader, className, bytes, 0, bytes.length); } /** * 永続ユニット情報で管理されるクラスを指定のクラスローダにロードします。 * * @param unitInfo * 永続ユニット情報 * @param loader * クラスローダ */ protected void loadPersistenceClasses(final PersistenceUnitInfo unitInfo, final ClassLoader loader) { for (final String className : unitInfo.getManagedClassNames()) { loadClass(loader, className); } for (final URL jarFileUrl : unitInfo.getJarFileUrls()) { loadClass(loader, jarFileUrl); } if (!unitInfo.excludeUnlistedClasses()) { final URL rootUrl = unitInfo.getPersistenceUnitRootUrl(); if ("file".equals(rootUrl.getProtocol())) { loadClass(loader, URLUtil.toFile(rootUrl), null); } else { loadClass(loader, FileUtil.toURL(new File(JarFileUtil .toJarFilePath(rootUrl)))); } } } /** * クラスをロードします。 * <p> * クラスが見つからない場合はクラス名をパッケージ名として解釈し、<code>package-info</code>クラスをロードします。 * </p> * * @param loader * クラスローダ * @param className * クラス名 */ protected void loadClass(final ClassLoader loader, final String className) { try { loader.loadClass(className); } catch (final ClassNotFoundException e) { ClassLoaderUtil.loadClass(loader, className + ".package-info"); } } /** * Jarファイルからクラスをロードします。 * * @param loader * クラスローダ * @param jarFileUrl * JarファイルのURL */ protected void loadClass(final ClassLoader loader, final URL jarFileUrl) { final JarFile jarFile = JarFileUtil.create(jarFileUrl.getPath()); try { for (final JarEntry entry : iterable(jarFile.entries())) { final String entryName = entry.getName(); if (entry.isDirectory() || !entryName.endsWith(".class")) { continue; } final String className = removeExtension(entryName).replace( '/', '.'); loadClass(loader, className); } } finally { JarFileUtil.close(jarFile); } } /** * ファイルシステムからクラスをロードします。 * * @param loader * クラスローダ * @param dir * ディレクトリ * @param path * クラスパスの基点から現在のディレクトリまでのパス (ピリオド区切り) */ protected void loadClass(final ClassLoader loader, final File dir, final String path) { for (final File file : dir.listFiles()) { final String fileName = file.getName(); if (file.isDirectory()) { loadClass(loader, file, ClassUtil.concatName(path, fileName)); } else if (fileName.endsWith(".class")) { final String className = ClassUtil.concatName(path, removeExtension(fileName)); loadClass(loader, className); } } } /** * 永続クラスのバイト列をトランスフォームしたバイト列を返します。 * * @param transformer * トランスフォーマ * @param classLoader * 変換されるクラスを定義するローダ * @param className * クラス名 * @param bytes * クラスファイル形式のバイト列 * @return 変換されたクラスファイル形式のバイト列。変換されなかった場合は引数のバイト列 */ protected byte[] transform(final ClassTransformer transformer, final ClassLoader classLoader, final String className, final byte[] bytes) { final byte[] transformed = ClassTransformerUtil.transform(transformer, classLoader, className.replace('.', '/'), null, null, bytes); return transformed == null ? bytes : transformed; } /** * トランスフォームした永続クラスをロードする対象のクラスローダを返します。 * * @param originLoader * 原点となるクラスローダ * @return トランスフォームした永続クラスをロードする対象のクラスローダ */ protected ClassLoader getTargetClassLoader(final ClassLoader originLoader) { ClassLoader loader = originLoader; while (loader != null && ignoreLoaderClassNames.contains(loader.getClass().getName())) { loader = loader.getParent(); } if (loader != null) { return loader; } return originLoader; } /** * ファイル名から拡張子を取り除いた名前を返します。 * * @param name * ファイル名 * @return ファイル名から拡張子を取り除いた名前 */ protected String removeExtension(final String name) { return name.substring(0, name.lastIndexOf('.')); } }