/**
* License Agreement.
*
* Rich Faces - Natural Ajax for Java Server Faces (JSF)
*
* Copyright (C) 2007 Exadel, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License version 2.1 as published by the Free Software Foundation.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.ajax4jsf.resource;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.StreamCorruptedException;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
import javax.faces.FacesException;
import javax.faces.context.FacesContext;
import javax.imageio.ImageIO;
import javax.servlet.ServletContext;
import org.ajax4jsf.Messages;
import org.ajax4jsf.resource.util.URLToStreamHelper;
import org.ajax4jsf.util.base64.Codec;
import org.ajax4jsf.webapp.WebXml;
import org.apache.commons.digester.Digester;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* Produce instances of InternetResource's for any types - jar resource, dynamic
* created image, component-incapsulated etc. Realised as singleton class to
* support cache, configuration etc.
*
* @author shura (latest modification by $Author: alexsmirnov $)
* @version $Revision: 1.1.2.1 $ $Date: 2007/01/09 18:56:58 $
*
*/
public class ResourceBuilderImpl extends InternetResourceBuilder {
private static final Log log = LogFactory.getLog(ResourceBuilderImpl.class);
private static final String DATA_SEPARATOR = "/DATA/";
private static final String DATA_BYTES_SEPARATOR = "/DATB/";
private static final Pattern DATA_SEPARATOR_PATTERN = Pattern
.compile("/DAT(A|B)/");
private static final ResourceRenderer defaultRenderer = new MimeRenderer(null);
private Map<String, ResourceRenderer> renderers;
/**
* keep resources instances . TODO - put this map to application-scope
* attribute, for support clastering environment.
*/
private Map<String, InternetResource> resources = Collections.synchronizedMap(new HashMap<String, InternetResource>());
private long _startTime;
private Codec codec;
private static final ResourceRenderer scriptRenderer = new ScriptRenderer();
private static final ResourceRenderer styleRenderer = new StyleRenderer();
static {
// renderers.put(".htc",new BehaviorRenderer());
// set in-memory caching ImageIO
Thread thread = Thread.currentThread();
ClassLoader initialTCCL = thread.getContextClassLoader();
try {
ClassLoader systemCL = ClassLoader.getSystemClassLoader();
thread.setContextClassLoader(systemCL);
ImageIO.setUseCache(false);
} finally {
thread.setContextClassLoader(initialTCCL);
}
}
public WebXml getWebXml(FacesContext context) {
WebXml webXml = WebXml.getInstance(context);
if (null == webXml) {
throw new FacesException(
"Resources framework is not initialised, check web.xml for Filter configuration");
}
return webXml;
}
public ResourceBuilderImpl() {
super();
_startTime = System.currentTimeMillis();
codec = new Codec();
renderers = new HashMap<String, ResourceRenderer>();
// append known renderers for extentions.
renderers.put(".gif", new GifRenderer());
ResourceRenderer renderer = new JpegRenderer();
renderers.put(".jpeg", renderer);
renderers.put(".jpg", renderer);
renderers.put(".png", new PngRenderer());
renderers.put(".js", getScriptRenderer());
renderers.put(".css", getStyleRenderer());
renderers.put(".log", new LogfileRenderer());
renderers.put(".html", new HTMLRenderer());
renderers.put(".xhtml", new MimeRenderer("application/xhtml+xml"));
renderers.put(".xml", new MimeRenderer("text/xml"));
renderers.put(".xcss", new TemplateCSSRenderer());
renderers.put(".swf", new MimeRenderer("application/x-shockwave-flash"));
}
/**
* @throws FacesException
*/
protected void registerResources() throws FacesException {
try {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Enumeration<URL> e = loader
.getResources("META-INF/resources-config.xml");
while (e.hasMoreElements()) {
URL resource = e.nextElement();
registerConfig(resource);
}
} catch (IOException e) {
throw new FacesException(e);
}
}
private void registerConfig(URL resourceConfig) {
try {
if (log.isDebugEnabled()) {
log.debug("Process resources configuration file "
+ resourceConfig.toExternalForm());
}
InputStream in = URLToStreamHelper.urlToStream(resourceConfig);
try {
Digester digester = new Digester();
digester.setValidating(false);
digester.setEntityResolver(new EntityResolver() {
// Dummi resolver - alvays do nothing
public InputSource resolveEntity(String publicId,
String systemId) throws SAXException, IOException {
return new InputSource(new StringReader(""));
}
});
digester.setNamespaceAware(false);
digester.setUseContextClassLoader(true);
digester.push(this);
digester.addObjectCreate("resource-config/resource", "class",
JarResource.class);
digester.addObjectCreate("resource-config/resource/renderer",
"class", HTMLRenderer.class);
digester.addCallMethod(
"resource-config/resource/renderer/content-type",
"setContentType", 0);
digester.addSetNext("resource-config/resource/renderer",
"setRenderer", ResourceRenderer.class.getName());
digester.addCallMethod("resource-config/resource/name",
"setKey", 0);
digester.addCallMethod("resource-config/resource/path",
"setPath", 0);
digester.addCallMethod("resource-config/resource/cacheable",
"setCacheable", 0);
digester.addCallMethod(
"resource-config/resource/session-aware",
"setSessionAware", 0);
digester.addCallMethod("resource-config/resource/property",
"setProperty", 2);
digester.addCallParam("resource-config/resource/property/name",
0);
digester.addCallParam(
"resource-config/resource/property/value", 1);
digester.addCallMethod("resource-config/resource/content-type",
"setContentType", 0);
digester.addSetNext("resource-config/resource", "addResource",
InternetResource.class.getName());
digester.parse(in);
} finally {
in.close();
}
} catch (IOException e) {
throw new FacesException(e);
} catch (SAXException e) {
throw new FacesException(e);
}
}
/**
*/
public void init() throws FacesException {
// TODO - mace codec configurable.
registerResources();
}
/**
* Base point for creating resource. Must detect type and build appropriate
* instance. Currently - make static resource for ordinary request, or
* instance of class.
*
* @param base
* base object for resource ( resourcess in classpath will be get
* relative to it package )
* @param path
* key - path to resource, resource class name etc.
* @return
* @throws FacesException
*/
public InternetResource createResource(Object base, String path)
throws FacesException {
// TODO - detect type of resource ( for example, resources location path
// in Skin
try {
return getResource(path);
} catch (ResourceNotFoundException e) {
try {
return getResource(buildKey(base, path));
} catch (ResourceNotFoundException e1) {
if (log.isDebugEnabled()) {
log.debug(Messages.getMessage(Messages.BUILD_RESOURCE_INFO,
path));
}
}
}
// path - is class name ?
InternetResource res;
try {
Class<?> resourceClass = Class.forName(path);
res = createDynamicResource(path, resourceClass);
} catch (Exception e) {
try {
res = createJarResource(base, path);
} catch (ResourceNotFoundException ex) {
res = createStaticResource(path);
}
// TODO - if resource not found, create static ?
}
return res;
}
private String buildKey(Object base, String path) {
if (path.startsWith("/")) {
return path.substring(1);
}
if (null == base) {
return path;
}
StringBuffer packageName = new StringBuffer(base.getClass()
.getPackage().getName().replace('.', '/'));
return packageName.append("/").append(path).toString();
}
public String getUri(InternetResource resource, FacesContext context,
Object storeData) {
StringBuffer uri = new StringBuffer();// ResourceServlet.DEFAULT_SERVLET_PATH).append("/");
uri.append(resource.getKey());
// append serialized data as Base-64 encoded request string.
if (storeData != null) {
try {
byte[] objectData;
if (storeData instanceof byte[]) {
objectData = (byte[]) storeData;
uri.append(DATA_BYTES_SEPARATOR);
} else {
ByteArrayOutputStream dataSteram = new ByteArrayOutputStream(
1024);
ObjectOutputStream objStream = new ObjectOutputStream(
dataSteram);
objStream.writeObject(storeData);
objStream.flush();
objStream.close();
dataSteram.close();
objectData = dataSteram.toByteArray();
uri.append(DATA_SEPARATOR);
}
byte[] dataArray = encrypt(objectData);
uri.append(new String(dataArray, "ISO-8859-1"));
// / byte[] objectData = dataSteram.toByteArray();
// / uri.append("?").append(new
// String(Base64.encodeBase64(objectData),
// / "ISO-8859-1"));
} catch (Exception e) {
// Ignore errors, log it
log.error(Messages
.getMessage(Messages.QUERY_STRING_BUILDING_ERROR), e);
}
}
boolean isGlobal = !resource.isSessionAware();
String resourceURL = getWebXml(context).getFacesResourceURL(context,
uri.toString(), isGlobal);// context.getApplication().getViewHandler().getResourceURL(context,uri.toString());
if (!isGlobal) {
resourceURL = context.getExternalContext().encodeResourceURL(
resourceURL);
}
if (log.isDebugEnabled()) {
log.debug(Messages.getMessage(Messages.BUILD_RESOURCE_URI_INFO,
resource.getKey(), resourceURL));
}
return resourceURL;// context.getExternalContext().encodeResourceURL(resourceURL);
}
/**
* @param key
* @return
*/
public InternetResource getResourceForKey(String key)
throws ResourceNotFoundException {
Matcher matcher = DATA_SEPARATOR_PATTERN.matcher(key);
if (matcher.find()) {
int data = matcher.start();
key = key.substring(0, data);
}
return getResource(key);
}
public Object getResourceDataForKey(String key) {
Object data = null;
String dataString = null;
Matcher matcher = DATA_SEPARATOR_PATTERN.matcher(key);
if (matcher.find()) {
if (log.isDebugEnabled()) {
log.debug(Messages.getMessage(
Messages.RESTORE_DATA_FROM_RESOURCE_URI_INFO, key,
dataString));
}
int dataStart = matcher.end();
dataString = key.substring(dataStart);
byte[] objectArray = null;
byte[] dataArray;
try {
dataArray = dataString.getBytes("ISO-8859-1");
objectArray = decrypt(dataArray);
} catch (UnsupportedEncodingException e1) {
// default encoding always presented.
}
if ("B".equals(matcher.group(1))) {
data = objectArray;
} else {
try {
ObjectInputStream in = new ObjectInputStream(
new ByteArrayInputStream(objectArray));
data = in.readObject();
} catch (StreamCorruptedException e) {
log.error(Messages
.getMessage(Messages.STREAM_CORRUPTED_ERROR), e);
} catch (IOException e) {
log.error(Messages
.getMessage(Messages.DESERIALIZE_DATA_INPUT_ERROR),
e);
} catch (ClassNotFoundException e) {
log
.error(
Messages
.getMessage(Messages.DATA_CLASS_NOT_FOUND_ERROR),
e);
}
}
}
return data;
}
public InternetResource getResource(String path)
throws ResourceNotFoundException {
InternetResource internetResource = (InternetResource) resources
.get(path);
if (null == internetResource) {
throw new ResourceNotFoundException("Resource not registered : "
+ path);
} else {
return internetResource;
}
}
public void addResource(InternetResource resource) {
resources.put(resource.getKey(), resource);
ResourceRenderer renderer = resource.getRenderer(null);
if (renderer == null) {
setRenderer(resource, resource.getKey());
}
}
public void addResource(String key, InternetResource resource) {
resources.put(key, resource);
resource.setKey(key);
// TODO - set renderer ?
}
// public String getFacesResourceKey(HttpServletRequest request) {
// return getWebXml(context).getFacesResourceKey(request);
// }
public String getFacesResourceURL(FacesContext context, String Url, boolean isGlobal) {
return getWebXml(context).getFacesResourceURL(context, Url, isGlobal);
}
/**
* Build resource for link to static context in webapp.
*
* @param path
* @return
* @throws FacesException
*/
protected InternetResource createStaticResource(String path)
throws ResourceNotFoundException, FacesException {
FacesContext context = FacesContext.getCurrentInstance();
if (null != context) {
try {
URL in = context.getExternalContext().getResource(path);
if (null != in) {
InternetResourceBase res = new StaticResource(path);
setRenderer(res, path);
// Detect last modification time.
res.setLastModified(new Date(getStartTime()));
addResource(path, res);
return res;
}
} catch (MalformedURLException e) {
throw new ResourceNotFoundException(Messages.getMessage(
Messages.STATIC_RESOURCE_NOT_FOUND_ERROR, path),e);
}
}
throw new ResourceNotFoundException(Messages.getMessage(
Messages.STATIC_RESOURCE_NOT_FOUND_ERROR, path));
}
private void setRenderer(InternetResource res, String path)
throws FacesException {
int lastPoint = path.lastIndexOf('.');
if (lastPoint > 0) {
String ext = path.substring(lastPoint);
ResourceRenderer resourceRenderer = getRendererByExtension(ext);
if (null != resourceRenderer) {
res.setRenderer(resourceRenderer);
} else {
if (log.isDebugEnabled()) {
log.debug(Messages.getMessage(
Messages.NO_RESOURCE_REGISTERED_ERROR_2, path,
renderers.keySet()));
}
// String mimeType = servletContext.getMimeType(path);
res.setRenderer(defaultRenderer);
}
}
}
/**
* @param ext
* @return
*/
protected ResourceRenderer getRendererByExtension(String ext) {
return renderers
.get(ext);
}
/**
* Create resurce to send from classpath relative to base class.
*
* @param base
* @param path
* @return
* @throws FacesException
*/
protected InternetResource createJarResource(Object base, String path)
throws ResourceNotFoundException, FacesException {
String key = buildKey(base, path);
ClassLoader loader = Thread.currentThread().getContextClassLoader();
if (null != loader.getResource(key)) {
JarResource res = new JarResource(key);
setRenderer(res, path);
res.setLastModified(new Date(getStartTime()));
addResource(key, res);
return res;
} else {
throw new ResourceNotFoundException(Messages.getMessage(
Messages.NO_RESOURCE_EXISTS_ERROR, key));
}
}
/**
* Create resource by instatiate given class.
*
* @param path
* @param instatiate
* @return
*/
protected InternetResource createDynamicResource(String path,
Class<?> instatiate) throws ResourceNotFoundException {
if (InternetResource.class.isAssignableFrom(instatiate)) {
InternetResource resource;
try {
resource = (InternetResource) instatiate.newInstance();
addResource(path, resource);
} catch (Exception e) {
String message = Messages.getMessage(
Messages.INSTANTIATE_RESOURCE_ERROR, instatiate
.toString());
log.error(message, e);
throw new ResourceNotFoundException(message, e);
}
return resource;
}
throw new FacesException(Messages
.getMessage(Messages.INSTANTIATE_CLASS_ERROR));
}
/**
* Create resource by instatiate {@link UserResource} class with given
* properties ( or got from cache ).
*
* @param cacheable
* @param session
* @param mime
* @return
* @throws FacesException
*/
public InternetResource createUserResource(boolean cacheable,
boolean session, String mime) throws FacesException {
String path = getUserResourceKey(cacheable, session, mime);
InternetResource userResource;
try {
userResource = getResource(path);
} catch (ResourceNotFoundException e) {
userResource = new UserResource(cacheable, session, mime);
addResource(path, userResource);
}
return userResource;
}
/**
* Generate resource key for user-generated resource with given properties.
*
* @param cacheable
* @param session
* @param mime
* @return
*/
private String getUserResourceKey(boolean cacheable, boolean session,
String mime) {
StringBuffer pathBuffer = new StringBuffer(UserResource.class.getName());
pathBuffer.append(cacheable ? "/c" : "/n");
pathBuffer.append(session ? "/s" : "/n");
if (null != mime) {
pathBuffer.append('/').append(mime.hashCode());
}
String path = pathBuffer.toString();
return path;
}
/**
* @return Returns the startTime for application.
*/
public long getStartTime() {
return _startTime;
}
protected byte[] encrypt(byte[] src) {
try {
Deflater compressor = new Deflater(Deflater.BEST_SPEED);
byte[] compressed = new byte[src.length + 100];
compressor.setInput(src);
compressor.finish();
int totalOut = compressor.deflate(compressed);
byte[] zipsrc = new byte[totalOut];
System.arraycopy(compressed, 0, zipsrc, 0, totalOut);
compressor.end();
return codec.encode(zipsrc);
} catch (Exception e) {
throw new FacesException("Error encode resource data", e);
}
}
protected byte[] decrypt(byte[] src) {
try {
byte[] zipsrc = codec.decode(src);
Inflater decompressor = new Inflater();
byte[] uncompressed = new byte[zipsrc.length * 5];
decompressor.setInput(zipsrc);
int totalOut = decompressor.inflate(uncompressed);
byte[] out = new byte[totalOut];
System.arraycopy(uncompressed, 0, out, 0, totalOut);
decompressor.end();
return out;
} catch (Exception e) {
throw new FacesException("Error decode resource data", e);
}
}
@Override
public ResourceRenderer getScriptRenderer() {
return scriptRenderer;
}
@Override
public ResourceRenderer getStyleRenderer() {
return styleRenderer;
}
}