(0001658)
|
KnisterPeter
|
12-18-06 04:19
|
|
I've implemented a patch to support other JPA implementations. Tested with Oracle Toplink Essentials.
Below is the source for the patched InjectIntrospector.java and an implementation class. Feel free to use this a contribution to resin if you are interested.
/*
* Copyright (c) 1998-2006 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
*
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.config.j2ee;
import java.beans.Introspector;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Resource;
import javax.annotation.Resources;
import javax.ejb.EJB;
import javax.naming.InitialContext;
import javax.naming.NameClassPair;
import javax.naming.NamingEnumeration;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceUnit;
import javax.transaction.UserTransaction;
import javax.xml.ws.Service;
import javax.xml.ws.WebServiceRef;
import com.caucho.config.BuilderProgram;
import com.caucho.config.ConfigException;
import com.caucho.naming.Jndi;
import com.caucho.util.L10N;
import com.caucho.util.Log;
/**
* Analyzes a bean for
*
* @Inject tags.
*/
public class InjectIntrospector {
private static final L10N L = new L10N(InjectIntrospector.class);
private static final Logger log = Log.open(InjectIntrospector.class);
/**
* Analyzes a bean for
*
* @Inject tags, building an init program for them.
*/
public static void configure(Object obj) throws Throwable {
if (obj != null) {
for (BuilderProgram program : introspect(obj.getClass())) {
program.configure(obj);
}
}
}
/**
* Analyzes a bean for
*
* @Inject tags, building an init program for them.
*/
public static InjectProgram introspectProgram(Class type)
throws ConfigException {
return new InjectProgram(introspect(type));
}
/**
* Analyzes a bean for
*
* @Inject tags, building an init program for them.
*/
public static ArrayList<BuilderProgram> introspectStatic(Class type)
throws ConfigException {
return introspect(type);
}
/**
* Analyzes a bean for
*
* @Inject tags, building an init program for them.
*/
public static ArrayList<BuilderProgram> introspect(Class type)
throws ConfigException {
ArrayList<BuilderProgram> initList = new ArrayList<BuilderProgram>();
try {
introspectImpl(initList, type);
} catch (ClassNotFoundException e) {
} catch (Error e) {
}
return initList;
}
private static void introspectImpl(ArrayList<BuilderProgram> initList,
Class type) throws ConfigException, ClassNotFoundException {
if (type == null || type.equals(Object.class))
return;
introspectImpl(initList, type.getSuperclass());
configureClassResources(initList, type);
for (Method method : type.getDeclaredMethods()) {
String fieldName = method.getName();
Class[] param = method.getParameterTypes();
if (param.length != 1)
continue;
if (fieldName.startsWith("set") && fieldName.length() > 3) {
fieldName = fieldName.substring(3);
char ch = fieldName.charAt(0);
if (Character.isUpperCase(ch)
&& (fieldName.length() == 1 || Character
.isLowerCase(fieldName.charAt(1)))) {
fieldName = Character.toLowerCase(ch)
+ fieldName.substring(1);
}
}
configure(initList, method, fieldName, param[0]);
}
}
public static void configureClassResources(
ArrayList<BuilderProgram> initList, Class type)
throws ConfigException {
Resources resources = (Resources) type.getAnnotation(Resources.class);
if (resources != null) {
for (Resource resource : resources.value()) {
introspectClassResource(initList, type, resource);
}
}
Resource resource = (Resource) type.getAnnotation(Resource.class);
if (resource != null) {
introspectClassResource(initList, type, resource);
}
for (Field field : type.getDeclaredFields()) {
configure(initList, field, field.getName(), field.getType());
}
}
private static void introspectClassResource(
ArrayList<BuilderProgram> initList, Class type, Resource resource)
throws ConfigException {
String name = resource.name();
Field field = findField(type, name);
if (field != null) {
initList.add(configureResource(field, field.getName(), field
.getType(), resource.name(), resource.type().getName(),
resource.name()));
return;
}
Method method = findMethod(type, name);
if (method != null) {
initList.add(configureResource(method, method.getName(), method
.getParameterTypes()[0], resource.name(), resource.type()
.getName(), resource.name()));
return;
}
}
private static Field findField(Class type, String name) {
for (Field field : type.getDeclaredFields()) {
if (field.getName().equals(name))
return field;
}
return null;
}
private static Method findMethod(Class type, String name) {
for (Method method : type.getDeclaredMethods()) {
if (method.getParameterTypes().length != 1)
continue;
String methodName = method.getName();
if (!methodName.startsWith("set"))
continue;
methodName = Introspector.decapitalize(methodName.substring(3));
if (name.equals(methodName))
return method;
}
return null;
}
public static void configure(ArrayList<BuilderProgram> initList,
AccessibleObject field, String fieldName, Class fieldType)
throws ConfigException {
if (field.isAnnotationPresent(Resource.class))
configureResource(initList, field, fieldName, fieldType);
else if (field.isAnnotationPresent(EJB.class))
configureEJB(initList, field, fieldName, fieldType);
else if (field.isAnnotationPresent(PersistenceUnit.class))
configurePersistenceUnit(initList, field, fieldName, fieldType);
else if (field.isAnnotationPresent(PersistenceContext.class))
configurePersistenceContext(initList, field, fieldName, fieldType);
else if (field.isAnnotationPresent(WebServiceRef.class))
configureWebServiceRef(initList, field, fieldName, fieldType);
}
private static void configureResource(ArrayList<BuilderProgram> initList,
AccessibleObject field, String fieldName, Class fieldType)
throws ConfigException {
Resource resource = field.getAnnotation(Resource.class);
initList.add(configureResource(field, fieldName, fieldType, resource
.name(), resource.type().getName(), resource.name()));
}
public static BuilderProgram introspectResource(AccessibleObject field,
String fieldName, Class fieldType) throws ConfigException {
Resource resource = field.getAnnotation(Resource.class);
if (resource != null)
return configureResource(field, fieldName, fieldType, resource
.name(), resource.type().getName(), resource.name());
else
return null;
}
private static void configureEJB(ArrayList<BuilderProgram> initList,
AccessibleObject field, String fieldName, Class fieldType)
throws ConfigException {
EJB ejb = (EJB) field.getAnnotation(javax.ejb.EJB.class);
initList.add(configureResource(field, fieldName, fieldType, ejb
.beanName(), "javax.ejb.EJBLocalObject", ejb.name()));
}
private static void configureWebServiceRef(
ArrayList<BuilderProgram> initList, AccessibleObject field,
String fieldName, Class fieldType) throws ConfigException {
WebServiceRef ref = (WebServiceRef) field
.getAnnotation(WebServiceRef.class);
String name = ref.name();
name = ref.name();
if ("".equals(name))
name = fieldName;
name = toFullName(name);
// XXX: types
AccessibleInject inject;
if (field instanceof Field)
inject = new FieldInject((Field) field);
else
inject = new PropertyInject((Method) field);
BuilderProgram program;
if (Service.class.isAssignableFrom(fieldType)) {
program = new ServiceInjectProgram(name, fieldType, inject);
} else {
program = new ServiceProxyInjectProgram(name, fieldType, inject);
}
initList.add(program);
}
private static void configurePersistenceUnit(
ArrayList<BuilderProgram> initList, AccessibleObject field,
String fieldName, Class fieldType) throws ConfigException {
PersistenceUnit pUnit = field.getAnnotation(PersistenceUnit.class);
String jndiPrefix = "java:comp/env/persistence/_amber_PersistenceUnit";
String jndiName = null;
String unitName = pUnit.unitName();
try {
if (!unitName.equals(""))
jndiName = jndiPrefix + '/' + unitName;
else {
InitialContext ic = new InitialContext();
NamingEnumeration<NameClassPair> iter = ic.list(jndiPrefix);
if (iter == null) {
log.warning("Can't find configured PersistenceUnit");
return; // XXX: error?
}
String ejbJndiName = null;
while (iter.hasMore()) {
NameClassPair pair = iter.next();
if (pair.getName().equals("resin-ejb"))
ejbJndiName = jndiPrefix + '/' + pair.getName();
else {
jndiName = jndiPrefix + '/' + pair.getName();
break;
}
}
if (jndiName == null)
jndiName = ejbJndiName;
}
initList.add(configureResource(field, fieldName, fieldType,
unitName, "javax.persistence.EntityManagerFactory",
jndiName));
} catch (Throwable e) {
log.log(Level.WARNING, e.toString(), e);
}
}
private static void configurePersistenceContext(
ArrayList<BuilderProgram> initList, AccessibleObject field,
String fieldName, Class fieldType) throws ConfigException {
PersistenceContext pContext = field
.getAnnotation(PersistenceContext.class);
String jndiPrefix = "java:comp/env/persistence";
String jndiName = null;
String unitName = pContext.unitName();
try {
if (!unitName.equals(""))
jndiName = jndiPrefix + '/' + unitName;
else {
InitialContext ic = new InitialContext();
NamingEnumeration<NameClassPair> iter = ic.list(jndiPrefix);
if (iter == null) {
log.warning("Can't find configured PersistenceContext");
return; // XXX: error?
}
String ejbJndiName = null;
while (iter.hasMore()) {
NameClassPair pair = iter.next();
// Skip reserved prefixes.
// See com.caucho.amber.manager.AmberContainer
if (pair.getName().startsWith("_amber"))
continue;
if (pair.getName().equals("resin-ejb"))
ejbJndiName = jndiPrefix + '/' + pair.getName();
else {
jndiName = jndiPrefix + '/' + pair.getName();
break;
}
}
if (jndiName == null)
jndiName = ejbJndiName;
}
// markusw (20061213)
// Added for third-party PersistenceProviders
if (Jndi.lookup(jndiName) == null) {
EntityManagerFactory emf = ThirdPartyPersistenceProviderConfigurator
.createFactory(unitName);
if (emf != null) {
log
.info("Binding ThirdParty EntityManagerFactory to JNDI: "
+ jndiName);
Jndi.bindDeep(jndiName, emf.createEntityManager());
}
}
initList.add(configureResource(field, fieldName, fieldType,
unitName, "javax.persistence.EntityManager", jndiName));
} catch (Throwable e) {
log.log(Level.WARNING, e.toString(), e);
}
}
private static BuilderProgram configureResource(AccessibleObject field,
String fieldName, Class fieldType, String name,
String resourceType, String jndiName) throws ConfigException {
String prefix = "";
if (name.equals(""))
name = fieldName;
if (resourceType.equals("") || resourceType.equals("java.lang.Object"))
resourceType = fieldType.getName();
if (resourceType.equals("javax.sql.DataSource"))
prefix = "jdbc/";
else if (resourceType.startsWith("javax.jms."))
prefix = "jms/";
else if (resourceType.startsWith("javax.mail."))
prefix = "mail/";
else if (resourceType.equals("java.net.URL"))
prefix = "url/";
else if (resourceType.startsWith("javax.ejb."))
prefix = "ejb/";
if (!jndiName.equals("")) {
} else if (UserTransaction.class.equals(fieldType)) {
jndiName = "java:comp/UserTransaction";
} else if ("java.util.concurrent.Executor".equals(resourceType)) {
jndiName = "java:comp/ThreadPool";
} else {
jndiName = prefix + name;
}
int colon = jndiName.indexOf(':');
int slash = jndiName.indexOf('/');
if (colon < 0 || slash > 0 && slash < colon)
jndiName = "java:comp/env/" + jndiName;
BuilderProgram program;
if (field instanceof Method)
program = new JndiInjectProgram(jndiName, (Method) field);
else
program = new JndiFieldInjectProgram(jndiName, (Field) field);
if (log.isLoggable(Level.FINEST))
log.log(Level.FINEST, String.valueOf(program));
return program;
}
private static String toFullName(String jndiName) {
int colon = jndiName.indexOf(':');
int slash = jndiName.indexOf('/');
if (colon < 0 || slash > 0 && slash < colon)
jndiName = "java:comp/env/" + jndiName;
return jndiName;
}
}
/*
* Copyright (c) 2006, New Media Markets & Networks NMMN -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
* Free SoftwareFoundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author markusw
*/
package com.caucho.config.j2ee;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.persistence.EntityManagerFactory;
import javax.persistence.spi.ClassTransformer;
import javax.persistence.spi.PersistenceProvider;
import javax.persistence.spi.PersistenceUnitTransactionType;
import javax.sql.DataSource;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import com.caucho.loader.DynamicClassLoader;
import com.caucho.naming.Jndi;
public class ThirdPartyPersistenceProviderConfigurator {
private static final Logger logger = Logger
.getLogger(ThirdPartyPersistenceProviderConfigurator.class
.getName());
static class PersistenceUnitInfo implements
javax.persistence.spi.PersistenceUnitInfo {
private String name;
private PersistenceUnitTransactionType transactionType;
private String provider;
private boolean excludeUnlistedClasses;
private List<URL> jarFileUrls = new ArrayList<URL>();
private DataSource jtaDataSource;
private DataSource nonJtaDataSource;
private List<String> managedClassNames = new ArrayList<String>();
private List<String> mappingFileNames = new ArrayList<String>();
private URL persistenceUnitRootUrl;
private Properties properties = new Properties();
public PersistenceUnitInfo(String name,
PersistenceUnitTransactionType transactionType) {
super();
this.name = name.trim();
this.transactionType = transactionType;
}
public void addTransformer(ClassTransformer transformer) {
// TODO: Implement
}
public boolean excludeUnlistedClasses() {
return excludeUnlistedClasses;
}
public void setExcludeUnlistedClasses(boolean excludeUnlistedClasses) {
this.excludeUnlistedClasses = excludeUnlistedClasses;
}
public ClassLoader getClassLoader() {
return Thread.currentThread().getContextClassLoader();
}
public ClassLoader getNewTempClassLoader() {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (cl instanceof DynamicClassLoader) {
return ((DynamicClassLoader) cl).getNewTempClassLoader();
}
return new URLClassLoader(null, cl);
}
public List<URL> getJarFileUrls() {
return jarFileUrls;
}
public DataSource getJtaDataSource() {
return jtaDataSource;
}
public void setJtaDataSource(DataSource jtaDataSource) {
this.jtaDataSource = jtaDataSource;
}
public List<String> getManagedClassNames() {
return managedClassNames;
}
public List<String> getMappingFileNames() {
return mappingFileNames;
}
public DataSource getNonJtaDataSource() {
return nonJtaDataSource;
}
public void setNonJtaDataSource(DataSource nonJtaDataSource) {
this.nonJtaDataSource = nonJtaDataSource;
}
public String getPersistenceProviderClassName() {
return provider;
}
public void setPersistenceProviderClassName(String provider) {
this.provider = provider.trim();
}
public String getPersistenceUnitName() {
return name;
}
public URL getPersistenceUnitRootUrl() {
return persistenceUnitRootUrl;
}
public void setPersistenceUnitRootUrl(URL persistenceUnitRootUrl) {
this.persistenceUnitRootUrl = persistenceUnitRootUrl;
}
public Properties getProperties() {
return properties;
}
public PersistenceUnitTransactionType getTransactionType() {
return transactionType;
}
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return getClass().getName() + "[name=" + name + ", provider="
+ provider + "]";
}
}
static class PersistenceXmlHandler extends DefaultHandler {
private List<PersistenceUnitInfo> infos = new ArrayList<PersistenceUnitInfo>();
private PersistenceUnitInfo info;
private StringBuilder chars = new StringBuilder();
public PersistenceUnitInfo[] getPersistenceUnitInfos() {
return infos.toArray(new PersistenceUnitInfo[infos.size()]);
}
/**
* @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String,
* java.lang.String, java.lang.String, org.xml.sax.Attributes)
*/
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
logger.finest("persistence.xml: <" + qName + ">");
if (qName.equals("persistence-unit")) {
logger
.warning("Created PersistenceUnitInfo for persistence-unit "
+ attributes.getValue("name"));
info = new PersistenceUnitInfo(attributes.getValue("name"),
PersistenceUnitTransactionType.valueOf(attributes
.getValue("transaction-type")));
infos.add(info);
} else if (qName.equals("property")) {
info.getProperties().put(attributes.getValue("name"),
attributes.getValue("value"));
}
}
/**
* @see org.xml.sax.helpers.DefaultHandler#characters(char[], int, int)
*/
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
chars.append(ch, start, length);
}
/**
* @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String,
* java.lang.String, java.lang.String)
*/
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
logger.finest("persistence.xml: </" + qName + ">");
if (qName.equals("provider")) {
info.setPersistenceProviderClassName(chars.toString().trim());
} else if (qName.equals("jta-data-source")) {
info.setJtaDataSource((DataSource) Jndi.lookup(chars.toString()
.trim()));
} else if (qName.equals("non-jta-data-source")) {
info.setNonJtaDataSource((DataSource) Jndi.lookup(chars
.toString().trim()));
} else if (qName.equals("mapping-file")) {
info.getMappingFileNames().add(chars.toString().trim());
} else if (qName.equals("jar-file")) {
try {
info.getJarFileUrls().add(new URL(chars.toString().trim()));
} catch (MalformedURLException e) {
e.printStackTrace();
}
} else if (qName.equals("class")) {
info.getManagedClassNames().add(chars.toString().trim());
} else if (qName.equals("exclude-unlisted-classes")) {
info.setExcludeUnlistedClasses(Boolean.parseBoolean(chars
.toString().trim()));
}
chars.setLength(0);
}
}
/**
* Added from Sun RI (Toplink Essentials) Persistence.java
*/
private static Set<PersistenceProvider> providers = new HashSet<PersistenceProvider>();
private static HashMap<String, PersistenceUnitInfo> infos = new HashMap<String, PersistenceUnitInfo>();
public static EntityManagerFactory createFactory(String unitName)
throws IOException {
if (providers.size() == 0) {
findAllProviders();
}
if (infos.size() == 0) {
parsePersistenceXmls();
}
logger.config("Searching PersistenceProvider");
for (PersistenceProvider provider : providers) {
PersistenceUnitInfo info = infos.get(unitName);
logger.config("PersistenceUnitInfo: " + info);
if (info != null
&& info.getPersistenceProviderClassName().equals(
provider.getClass().getName())) {
logger.config("Creating EntityManagerFactory from "
+ provider.getClass().getName());
EntityManagerFactory emf = provider
.createContainerEntityManagerFactory(info, null);
if (emf != null) {
logger.config("Returning EntityManagerFactory");
return emf;
}
}
}
return null;
}
/**
* Added from Sun RI (Toplink Essentials) Persistence.java
*/
private static void findAllProviders() throws IOException {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Enumeration<URL> resources = loader.getResources("META-INF/services/"
+ PersistenceProvider.class.getName());
Set<String> names = new HashSet<String>();
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
InputStream is = url.openStream();
try {
names.addAll(providerNamesFromReader(new BufferedReader(
new InputStreamReader(is))));
} finally {
is.close();
}
}
for (String s : names) {
try {
providers.add((PersistenceProvider) loader.loadClass(s)
.newInstance());
} catch (ClassNotFoundException exc) {
} catch (InstantiationException exc) {
} catch (IllegalAccessException exc) {
}
}
}
/**
* Added from Sun RI (Toplink Essentials) Persistence.java
*/
private static final Pattern nonCommentPattern = Pattern
.compile("^([^#]+)");
/**
* Added from Sun RI (Toplink Essentials) Persistence.java
*/
private static Set<String> providerNamesFromReader(BufferedReader reader)
throws IOException {
Set<String> names = new HashSet<String>();
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
Matcher m = nonCommentPattern.matcher(line);
if (m.find()) {
names.add(m.group().trim());
}
}
return names;
}
private static void parsePersistenceXmls() throws IOException {
// Search jar files
parsePersistenceXml("META-INF/persistence.xml");
// Search extracted webapps
parsePersistenceXml("../../META-INF/persistence.xml");
}
private static void parsePersistenceXml(String location) throws IOException {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Enumeration<URL> resources = loader.getResources(location);
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
InputStream in = resource.openStream();
try {
URL puRootUrl = getPersistenceUnitRootUrl(resource);
logger.config("PersistenceUnitRootUrl: " + puRootUrl);
PersistenceUnitInfo[] unitInfos = parsePersistenceXml(in);
for (PersistenceUnitInfo info : unitInfos) {
info.setPersistenceUnitRootUrl(puRootUrl);
infos.put(info.getPersistenceUnitName(), info);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
in.close();
}
}
}
private static PersistenceUnitInfo[] parsePersistenceXml(InputStream in)
throws Exception {
PersistenceXmlHandler handler = new PersistenceXmlHandler();
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser sp = spf.newSAXParser();
XMLReader parser = sp.getXMLReader();
parser.setContentHandler(handler);
parser.parse(new InputSource(in));
return handler.getPersistenceUnitInfos();
}
private static URL getPersistenceUnitRootUrl(URL resource)
throws IOException, URISyntaxException {
URLConnection conn = resource.openConnection();
if (conn instanceof JarURLConnection) {
return ((JarURLConnection) conn).getJarFileURL();
}
return new File(new File(conn.getURL().toURI()).getParentFile()
.getParentFile(), "WEB-INF/classes").toURL();
}
} |
|