Commit f88a0555 authored by Cyril Dangerville's avatar Cyril Dangerville

* Fixed Thread local memory leak (spotted when undeploying/redeploying

authzforce-webapp)
* Upgraded StaticPolicyFinderModule to support loading any Spring-like
resource URL
* Changed git repo URL after migration to FILAB
* Refactored resource loading used by
PdpModelHandler/PdpConfigurationManager/SchemaHandler -> new class
ResourceUtils (like in CXF) reusing only one Spring resource loader
parent 816dbece
......@@ -16,10 +16,10 @@
<inceptionYear>2011</inceptionYear>
<scm>
<!-- Used by Jenkins - Maven prepare-release -->
<connection>scm:git:https://matrice.dev.theresis.org/authzforce/authzforce-core.git</connection>
<developerConnection>scm:git:https://matrice.dev.theresis.org/authzforce/authzforce-core</developerConnection>
<connection>scm:git:https://gitlab.dev.theresis.org/authzforce/core.git</connection>
<developerConnection>scm:git:https://gitlab.dev.theresis.org/authzforce/core.git</developerConnection>
<tag>HEAD</tag>
<url>https://matrice.dev.theresis.org/authzforce/authzforce-core.git</url>
<url>https://gitlab.dev.theresis.org/authzforce/core.git</url>
</scm>
<properties>
<!-- JDK versions for AspectJ -->
......
......@@ -97,7 +97,6 @@ public abstract class AbstractPolicySet extends oasis.names.tc.xacml._3_0.core.s
*/
protected AbstractPolicySet()
{
}
/**
......
......@@ -38,7 +38,6 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
......@@ -82,6 +81,9 @@ import com.thalesgroup.authzforce.xacml.schema.XACMLCategory;
* these environment values to be cached, so that (for example) the current time remains constant
* over the course of an evaluation.
*
* Uses {@link Utils#THREAD_LOCAL_NS_AWARE_DOC_BUILDER}. Call
* {@code Utils.THREAD_LOCAL_NS_AWARE_DOC_BUILDER.remove()} after you are done (in finally block).
*
* @since 1.2
* @author Seth Proctor
*/
......@@ -521,13 +523,13 @@ public class BasicEvaluationCtx implements EvaluationCtx
attrValue.getContent().add(resourceId.encode());
}
public void setResourceId(List<AttributeValue> resourceId)
{
for (AttributeValue avts : resourceId)
{
this.setResourceId(avts);
}
}
// public void setResourceId(List<AttributeValue> resourceId)
// {
// for (AttributeValue avts : resourceId)
// {
// this.setResourceId(avts);
// }
// }
/**
* Returns the value for the current time. The current time, current date, and current dateTime
......@@ -915,7 +917,8 @@ public class BasicEvaluationCtx implements EvaluationCtx
LOGGER.debug("Attribute id='{}' not in request context... querying AttributeFinder", id);
/**
* <code>newAttrSet</code> is linked to the eval context map. So this context is updated by
* storing the new value from callHelper() (call to AttributeFInder) in <code>newAttrSet</code>.
* storing the new value from callHelper() (call to AttributeFInder) in
* <code>newAttrSet</code>.
*/
final EvaluationResult result = callHelper(type, id, issuer, category, designatorType);
final AttributeValueType resultAttrVal = result.getAttributeValue();
......
......@@ -62,6 +62,7 @@ import com.sun.xacml.finder.PolicyFinder;
import com.sun.xacml.finder.PolicyFinderResult;
import com.sun.xacml.finder.ResourceFinder;
import com.sun.xacml.finder.ResourceFinderResult;
import com.thalesgroup.appsec.util.Utils;
import com.thalesgroup.authzforce.audit.annotations.Audit;
import com.thalesgroup.authzforce.xacml.schema.XACMLCategory;
......@@ -172,6 +173,7 @@ public class PDP
/**
* Used to initiate a reload of the policies without reload the whole server
*
* @return the PolicyFinder used by the PDP
*/
public PolicyFinder getPolicyFinder()
......@@ -281,7 +283,7 @@ public class PDP
customs.add(myAttrs);
}
}
if (subjects.isEmpty() && actions.isEmpty() && resources.isEmpty())
{
// there was something wrong with the request, so we return
......@@ -337,24 +339,30 @@ public class PDP
}
}
}
for (Request requestList : requests)
try
{
for (Request requestList : requests)
{
requestList.getAttributes().addAll(environments);
// }
// for (Request requestList : requests)
// {
requestList.getAttributes().addAll(customs);
// }
// for (Request Request : requests)
// {
ResponseCtx response = this.evaluatePrivate(requestList);
responses.add(response);
}
} finally
{
requestList.getAttributes().addAll(environments);
// }
// for (Request requestList : requests)
// {
requestList.getAttributes().addAll(customs);
// }
// for (Request Request : requests)
// {
ResponseCtx response = this.evaluatePrivate(requestList);
responses.add(response);
Utils.THREAD_LOCAL_NS_AWARE_DOC_BUILDER.remove();
}
}
return responses;
}
}
/**
* Attempts to evaluate the request against the policies known to this PDP. This is really the
......@@ -373,21 +381,35 @@ public class PDP
* @deprecated Use evaluateList instead
*/
@Audit(type = Audit.Type.DISPLAY)
public ResponseCtx evaluate(Request request) {
return evaluatePrivate(request);
public ResponseCtx evaluate(Request request)
{
try
{
return evaluatePrivate(request);
} finally
{
Utils.THREAD_LOCAL_NS_AWARE_DOC_BUILDER.remove();
}
}
/**
* Uses {@code Utils#THREAD_LOCAL_NS_AWARE_DOC_BUILDER } Uses
* {@link Utils#THREAD_LOCAL_NS_AWARE_DOC_BUILDER}. Call
* {@code Utils.THREAD_LOCAL_NS_AWARE_DOC_BUILDER.remove()} after calling this method (in
* finally block).
*/
private ResponseCtx evaluatePrivate(Request request)
{
// try to create the EvaluationCtx out of the request
try
{
final BasicEvaluationCtx evalCtx = new BasicEvaluationCtx(request, attributeFinder, PolicyMetaData.XACML_VERSION_3_0);
// Using the cache if defined
if (cache != null && !cache.isDisabled())
{
// FIXME: simply use Request#hashCode() (generated by JAXB) instead of custom getHashCode()
// FIXME: simply use Request#hashCode() (generated by JAXB) instead of custom
// getHashCode()
final String cacheKey = getHashCode(request);
final Element cacheResult = cache.get(cacheKey);
LOGGER.debug("cache.get({}) -> '{}'", cacheKey, cacheResult);
......@@ -400,8 +422,8 @@ public class PDP
final ResponseCtx respCtx = evaluate(evalCtx);
cache.put(new Element(cacheKey, respCtx));
return respCtx;
}
}
return evaluate(evalCtx);
} catch (ParsingException pe)
{
......@@ -521,7 +543,7 @@ public class PDP
// return the set of results
return new ResponseCtx(results);
}
// the scope was IMMEDIATE (or missing), so we can just evaluate
// the request and return whatever we get back
return new ResponseCtx(evaluateContext(context));
......
......@@ -75,26 +75,26 @@ public class StatusDetail
* @throws IllegalArgumentException if there is a problem encoding the
* <code>Attribute</code>s
*/
public StatusDetail(List attributes) throws IllegalArgumentException {
detailText = "<StatusDetail>\n";
Iterator it = attributes.iterator();
while (it.hasNext()) {
Attribute attr = (Attribute)(it.next());
detailText += attr.encode() + "\n";
}
detailText += "</StatusDetail>";
try {
detailRoot = textToNode(detailText);
} catch (ParsingException pe) {
// really, this should never happen, since we just made sure that
// we're working with valid text, but it's possible that encoding
// the attribute could have caused problems...
throw new IllegalArgumentException("invalid Attribute data");
}
}
// public StatusDetail(List attributes) throws IllegalArgumentException {
// detailText = "<StatusDetail>\n";
// Iterator it = attributes.iterator();
//
// while (it.hasNext()) {
// Attribute attr = (Attribute)(it.next());
// detailText += attr.encode() + "\n";
// }
//
// detailText += "</StatusDetail>";
//
// try {
// detailRoot = textToNode(detailText);
// } catch (ParsingException pe) {
// // really, this should never happen, since we just made sure that
// // we're working with valid text, but it's possible that encoding
// // the attribute could have caused problems...
// throw new IllegalArgumentException("invalid Attribute data");
// }
// }
/**
* Constructor that takes the text-encoded form of the XML to use as
......@@ -107,10 +107,10 @@ public class StatusDetail
*
* @throws ParsingException if the encoded text is invalid XML
*/
public StatusDetail(String encoded) throws ParsingException {
detailText = "<StatusDetail>\n" + encoded + "\n</StatusDetail>";
detailRoot = textToNode(detailText);
}
// public StatusDetail(String encoded) throws ParsingException {
// detailText = "<StatusDetail>\n" + encoded + "\n</StatusDetail>";
// detailRoot = textToNode(detailText);
// }
/**
* Private constructor that just sets the root node. This interface
......@@ -123,19 +123,19 @@ public class StatusDetail
/**
* Private helper routine that converts text into a node
*/
private static Node textToNode(String encoded) throws ParsingException {
final DocumentBuilder db = Utils.THREAD_LOCAL_NS_UNAWARE_DOC_BUILDER.get();
try {
String text = "<?xml version=\"1.0\"?>\n";
byte [] bytes = (text + encoded).getBytes();
Document doc = db.parse(new ByteArrayInputStream(bytes));
return doc.getDocumentElement();
} catch (Exception e) {
throw new ParsingException("invalid XML for status detail");
} finally {
db.reset();
}
}
// private static Node textToNode(String encoded) throws ParsingException {
// final DocumentBuilder db = Utils.THREAD_LOCAL_NS_UNAWARE_DOC_BUILDER.get();
// try {
// String text = "<?xml version=\"1.0\"?>\n";
// byte [] bytes = (text + encoded).getBytes();
// Document doc = db.parse(new ByteArrayInputStream(bytes));
// return doc.getDocumentElement();
// } catch (Exception e) {
// throw new ParsingException("invalid XML for status detail");
// } finally {
// db.reset();
// }
// }
/**
* Creates an instance of a <code>StatusDetail</code> object based on
......
......@@ -34,7 +34,7 @@
package com.sun.xacml.support.finder;
import java.io.File;
import java.net.MalformedURLException;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
......@@ -63,14 +63,15 @@ import com.sun.xacml.xacmlv3.IPolicy;
import com.sun.xacml.xacmlv3.Policy;
import com.thalesgroup.authz.model.ext._3.AbstractPolicyFinder;
import com.thalesgroup.authzforce.core.PdpModelHandler;
import com.thalesgroup.authzforce.core.ResourceUtils;
/**
* This is a simple implementation of <code>PolicyFinderModule</code> that supports retrieval based
* on context, and is designed for use with a run-time configuration. Its constructor accepts a
* <code>List</code> of <code>String</code>s that represent URLs or files, and these are resolved to
* policies when the module is initialized. Beyond this, there is no modifying or re-loading the
* policies represented by this class. This class will optionally wrap multiple applicable policies
* into a dynamic PolicySet.
* <code>List</code> of <code>String</code>s that represent Spring-like URLs or files, and these are
* resolved to policies when the module is initialized. Beyond this, there is no modifying or
* re-loading the policies represented by this class. This class will optionally wrap multiple
* applicable policies into a dynamic PolicySet.
* <p>
* Note that this class is designed to complement <code>StaticRefPolicyFinderModule</code>. It would
* be easy to support both kinds of policy retrieval in a single class, but the functionality is
......@@ -275,24 +276,13 @@ public class StaticPolicyFinderModule extends PolicyFinderModule<AbstractPolicyF
}
}
/**
* Always returns <code>true</code> since this module does support finding policies based on
* context.
*
* @return true
*/
@Override
public boolean isRequestSupported()
{
return true;
}
/**
* Initialize this module. Typically this is called by <code>PolicyFinder</code> when a PDP is
* created. This method is where the policies are actually loaded.
*
* @param finder
* the <code>PolicyFinder</code> using this module
*/
@Override
public void init(PolicyFinder finder)
{
// now that we have the PolicyFinder, we can load the policies
......@@ -305,19 +295,25 @@ public class StaticPolicyFinderModule extends PolicyFinderModule<AbstractPolicyF
try
{
unmarshaller = PdpModelHandler.XACML_3_0_JAXB_CONTEXT.createUnmarshaller();
} catch (JAXBException e1)
{
throw new IllegalArgumentException("Failed to create JAXB marshaller for unmarshalling Policy XML document", e1);
}
catch (JAXBException e1)
{
throw new IllegalArgumentException("Failed to create JAXB marshaller for unmarshalling Policy XML document", e1);
unmarshaller.setSchema(schema);
try
{
// first try to load it as a Spring resource
final URL url = ResourceUtils.getResourceURL(policyLocation);
if(url == null) {
throw new IOException("Invalid Spring-supported URL: " + policyLocation);
}
unmarshaller.setSchema(schema);
try {
// first try to load it as a URL
final URL url = new URL(policyLocation);
jaxbObj = unmarshaller.unmarshal(url);
} catch (MalformedURLException murle)
} catch (IOException ioe)
{
LOGGER.info("Failed to load policy location {} as Spring resource. Loading as file relative to PDP configuration directory");
// assume that this is a filename, and try again
final File file = new File(policyLocation);
final File policyFile;
......@@ -328,7 +324,7 @@ public class StaticPolicyFinderModule extends PolicyFinderModule<AbstractPolicyF
{
policyFile = file;
}
try
{
jaxbObj = unmarshaller.unmarshal(policyFile);
......@@ -348,10 +344,10 @@ public class StaticPolicyFinderModule extends PolicyFinderModule<AbstractPolicyF
try
{
policyInstance = Policy.getInstance(policyElement);
} catch (ParsingException|UnknownIdentifierException e)
} catch (ParsingException | UnknownIdentifierException e)
{
throw new IllegalArgumentException("Error parsing Policy: " + policyElement.getPolicyId(), e);
}
}
} else if (jaxbObj instanceof oasis.names.tc.xacml._3_0.core.schema.wd_17.PolicySet)
{
final oasis.names.tc.xacml._3_0.core.schema.wd_17.PolicySet policySetElement = (oasis.names.tc.xacml._3_0.core.schema.wd_17.PolicySet) jaxbObj;
......@@ -378,90 +374,28 @@ public class StaticPolicyFinderModule extends PolicyFinderModule<AbstractPolicyF
}
}
/**
* TODO: Handle policySet
*
* Finds a policy based on a request's context. If more than one policy matches, then this
* either returns an error or a new policy wrapping the multiple policies (depending on which
* constructor was used to construct this instance).
*
* @param context
* the representation of the request data
*
* @return the result of trying to find an applicable policy
*/
// public PolicyFinderResult findPolicy(EvaluationCtx context) {
// try {
// Policy policy = (Policy)policies.getPolicy(context);
//
// if (policy == null) {
// return new PolicyFinderResult();
// } else {
// return new PolicyFinderResult(policy);
// }
// } catch (TopLevelPolicyException tlpe) {
// return new PolicyFinderResult(tlpe.getStatus());
// }
// }
@Override
public PolicyFinderResult findPolicy(EvaluationCtx context)
{
// try
// {
// final IPolicy policy = this.policies.getPolicy(context);
// if (policy instanceof PolicySet)
// {
// PolicySet policySet = (PolicySet) policy;
// // Retrieving combining algorithm
// PolicyCombiningAlgorithm myCombiningAlg = (PolicyCombiningAlgorithm) policySet.getCombiningAlg();
// PolicyCollection myPolcollection = new PolicyCollection(myCombiningAlg, URI.create(policySet.getPolicySetId()));
// for (Object elt : policySet.getPolicySetsAndPoliciesAndPolicySetIdReferences())
// {
// if (elt instanceof PolicyCombinerElement)
// {
// myPolcollection.addPolicy((Policy) ((PolicyCombinerElement) elt).getElement());
// }
// }
//
// Object policy = myPolcollection.getPolicy(context);
// // The finder found more than one applicable policy so it build
// // a new PolicySet
// if (policy instanceof PolicySet)
// {
// return new PolicyFinderResult((PolicySet) policy, myCombiningAlg);
// }
// // The finder found only one applicable policy
// else if (policy instanceof Policy)
// {
// return new PolicyFinderResult((Policy) policy);
// }
// } else if (myPolicies instanceof Policy)
// {
// Policy policies = (Policy) myPolicies;
// return new PolicyFinderResult((Policy) policies);
// }
// // None of the policies/policySets matched
// return new PolicyFinderResult();
// } catch (TopLevelPolicyException tlpe)
// {
// return new PolicyFinderResult(tlpe.getStatus());
// }
try {
final IPolicy policy = policies.getPolicy(context);
if (policy == null)
return new PolicyFinderResult();
else
return new PolicyFinderResult(policy);
} catch (TopLevelPolicyException tlpe) {
return new PolicyFinderResult(tlpe.getStatus());
}
try
{
final IPolicy policy = policies.getPolicy(context);
if (policy == null) {
return new PolicyFinderResult();
}
return new PolicyFinderResult(policy);
} catch (TopLevelPolicyException tlpe)
{
return new PolicyFinderResult(tlpe.getStatus());
}
}
@Override
public void init(AbstractPolicyFinder conf)
{
throw new UnsupportedOperationException("Initialization method not supported. Use the constructors instead.");
}
}
......@@ -89,7 +89,6 @@ import com.thalesgroup.authzforce.pdp.model._2014._12.XacmlFeatureIdToImplementa
public class PdpConfigurationManager
{
private final static Logger LOGGER = LoggerFactory.getLogger(PdpConfigurationManager.class);
private final static DefaultResourceLoader RESOURCE_LOADER = new DefaultResourceLoader();
private final Map<String, PDPConfig> pdpMap = new HashMap<>();
private final Map<String, com.sun.xacml.attr.AttributeFactory> attrFactoryMap = new HashMap<>();
private final Map<String, com.sun.xacml.combine.CombiningAlgFactory> combAlgFactoryMap = new HashMap<>();
......@@ -184,7 +183,7 @@ public class PdpConfigurationManager
*/
public PdpConfigurationManager(String confLocation, PdpModelHandler modelhandler) throws IOException, JAXBException
{
final Resource confResource = RESOURCE_LOADER.getResource(confLocation);
final Resource confResource = ResourceUtils.getResource(confLocation);
if (confResource == null || !confResource.exists())
{
throw new IllegalArgumentException("No resource available at this location: " + confLocation);
......
......@@ -64,13 +64,14 @@ public class PdpModelHandler
* Location of PDP configuration schema
*/
public final static String CORE_XSD_LOCATION = "classpath:pdp.xsd";
/**
* Default location of XML catalog to resolve imported XML schemas in {@value PdpModelHandler#CORE_XSD_LOCATION}
* Default location of XML catalog to resolve imported XML schemas in
* {@value PdpModelHandler#CORE_XSD_LOCATION}
*/
public final static String DEFAULT_CATALOG_LOCATION = "classpath:catalog.xml";
private final static String[] XACML_3_0_SCHEMA_LOCATIONS = {"classpath:xml.xsd", "classpath:xacml-core-v3-schema-wd-17.xsd"};
private final static String[] XACML_3_0_SCHEMA_LOCATIONS = { "classpath:xml.xsd", "classpath:xacml-core-v3-schema-wd-17.xsd" };
/**
* XPath to schema locations in XML schema imports
......@@ -90,7 +91,7 @@ public class PdpModelHandler
throw new RuntimeException("Invalid XPath to XSD import schemaLocation values: " + XSD_IMPORT_SCHEMA_LOCATIONS_XPATH, e);
}
}
/**
* XACML 3.0 XML schema namespace
*/
......@@ -110,11 +111,11 @@ public class PdpModelHandler
throw new RuntimeException("Error instantiating JAXB context for (un)marshalling from/to XACML 3.0 objects", e);
}
}
/**
* XACML 3.0 schema
*/
public static final Schema XACML_3_0_SCHEMA = SchemaHandler.createSchema(Arrays.asList(XACML_3_0_SCHEMA_LOCATIONS),null);
public static final Schema XACML_3_0_SCHEMA = SchemaHandler.createSchema(Arrays.asList(XACML_3_0_SCHEMA_LOCATIONS), null);
private final static Logger LOGGER = LoggerFactory.getLogger(PdpModelHandler.class);
......@@ -122,11 +123,12 @@ public class PdpModelHandler
* Supported JAXB types for root elements of XML configuration documents (e.g. files)
*/
private final static Class<?>[] SUPPORTED_ROOT_CONF_ELEMENT_JAXB_TYPES = { Pdps.class };
/**
* JAXB types of default extensions already provided by authzforce-core and defined in PDP core XSD.
* JAXB types of default extensions already provided by authzforce-core and defined in PDP core
* XSD.
*/
private final static Class<?>[] DEFAULT_EXTENSION_JAXB_TYPES = { CurrentDateTimeFinder.class, AttributeSelectorXPathFinder.class};
private final static Class<?>[] DEFAULT_EXTENSION_JAXB_TYPES = { CurrentDateTimeFinder.class, AttributeSelectorXPathFinder.class };
private Schema confSchema;
private final Class<?>[] extJaxbBoundClasses;
......@@ -201,7 +203,7 @@ public class PdpModelHandler
schemaLocations = Arrays.asList(extensionXsdLocation, CORE_XSD_LOCATION);
final Document extXsDoc;
final DocumentBuilder threadLocalDocBuilder = Utils.THREAD_LOCAL_NS_AWARE_DOC_BUILDER.get();
try (final InputStream extXsdIn = SchemaHandler.getResourceStream(extensionXsdLocation))
try (final InputStream extXsdIn = ResourceUtils.getResourceStream(extensionXsdLocation))
{
if (extXsdIn == null)
{
......@@ -224,6 +226,12 @@ public class PdpModelHandler
} finally
{
threadLocalDocBuilder.reset();
/*
* Clear the thread-local variable to prevent memory leaks, e.g. after webapp
* redeployment in Java Server, where server threads are recycled (not bound to the
* webapp lifecycle)
*/
Utils.THREAD_LOCAL_NS_AWARE_DOC_BUILDER.remove();
}
// load all user-defined imported extension schemas
......@@ -294,14 +302,16 @@ public class PdpModelHandler
// Load schema for validating XML configurations
final String schemaHandlerCatalogLocation;
if(catalogLocation == null) {
if (catalogLocation == null)
{
LOGGER.info("No XML catalog location specified for PDP schema handler, using default: {}", DEFAULT_CATALOG_LOCATION);
schemaHandlerCatalogLocation = DEFAULT_CATALOG_LOCATION;
} else {
} else
{
LOGGER.info("XML catalog location specified for PDP schema handler: {}", catalogLocation);
schemaHandlerCatalogLocation = catalogLocation;
}
confSchema = SchemaHandler.createSchema(schemaLocations, schemaHandlerCatalogLocation);
}
......
/**
* Copyright (C) 2011-2015 Thales Services SAS.
*
* This file is part of AuthZForce.
*
* AuthZForce 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 3 of the License, or
* (at your option) any later version.
*
* AuthZForce 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. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AuthZForce. If not, see <http://www.gnu.org/licenses/>.
*/
package com.thalesgroup.authzforce.core;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
/**
* (Spring-like) Resource loading utils (simplified org.apache.cxf.jaxrs.utils.ResourceUtils to
* avoid using this class directly which would require the full cxf-rt-frontend-jaxrs dependency,
* which we want to avoid. Maybe one day this class will be moved out of the jaxrs module, into the
* core module, this would be much better.)
*
*/
public class ResourceUtils
{
private static final DefaultResourceLoader RESOURCE_LOADER = new DefaultResourceLoader();
private static final Logger LOGGER = LoggerFactory.getLogger(SchemaHandler.class);
/**
* Get resource URL from Spring-supported resource location
*
* @param loc
* @return resource URL
* @throws IOException
*/
public static URL getResourceURL(String loc) throws IOException
{
final Resource resource = RESOURCE_LOADER.getResource(loc);
if (resource == null || !resource.exists())
{
LOGGER.warn("No resource '" + loc + "' is available");
return null;
}
final URL url = resource.getURL();
if (url == null)
{
LOGGER.warn("Resource " + loc + " could not be resolved to a URL");
}
return url;
}
//
// public static URL getClasspathResourceURL(String path, Class<?> callingClass)
// {
// return ClassLoaderUtils.getResource(path, callingClass);
// }
//
/**
* Get resource stream from Spring-supported resource location
*
* @param loc
* @return resource stream
* @throws Exception
*/
public static InputStream getResourceStream(String loc) throws Exception
{
final URL url = getResourceURL(loc);
return url == null ? null : url.openStream();
}
/**
* Same as {@link DefaultResourceLoader#getResource(String)}
* @param location
* @return resource handle
*/
public static Resource getResource(String location)
{