/* * JBoss, Home of Professional Open Source * Copyright 2013, Red Hat, Inc. and individual contributors * by the @authors tag. See the copyright.txt in the distribution for a * full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.richfaces.resource.optimizer.task; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.text.MessageFormat; import java.util.concurrent.Callable; import java.util.concurrent.CompletionService; import javax.activation.MimetypesFileTypeMap; import javax.faces.application.Resource; import javax.faces.application.ResourceHandler; import javax.faces.context.FacesContext; import org.richfaces.log.Logger; import org.richfaces.resource.ResourceKey; import org.richfaces.resource.ResourceSkinUtils; import org.richfaces.resource.optimizer.Faces; import org.richfaces.resource.optimizer.ResourceTaskFactory; import org.richfaces.resource.optimizer.ResourceWriter; import org.richfaces.resource.optimizer.faces.CurrentResourceContext; import org.richfaces.resource.optimizer.resource.util.ResourceConstants; import org.richfaces.resource.optimizer.resource.util.ResourceUtil; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import org.richfaces.util.StreamUtils; /** * @author Nick Belaevski * */ public class ResourceTaskFactoryImpl implements ResourceTaskFactory { private class ResourcesRendererCallable implements Callable<Object> { private ResourceKey resourceKey; private boolean skinDependent; private boolean skipped = false; ResourcesRendererCallable(ResourceKey resourceKey) { this.resourceKey = resourceKey; // when packaging JSF's JavaScript implementation, use uncompressed version // as double compression may lead in inability to use it if (packName != null && ResourceConstants.JSF_COMPRESSED.equals(resourceKey)) { this.resourceKey = ResourceConstants.JSF_UNCOMPRESSED; } } private Resource createResource(FacesContext facesContext, ResourceKey resourceInfo) { ResourceHandler resourceHandler = facesContext.getApplication().getResourceHandler(); return resourceHandler.createResource(resourceInfo.getResourceName(), resourceInfo.getLibraryName()); } private void renderResource(String skin) { log.debug("rendering " + resourceKey + " (" + skin + ")"); try { FacesContext facesContext = faces.startRequest(); if (skin != null) { faces.setSkin(skin); } Resource resource = createResource(facesContext, resourceKey); CurrentResourceContext.getInstance(facesContext).setResource(resource); // TODO check content type if (shouldCheckForEL(resource) && containsELExpression(resource)) { log.info(MessageFormat.format("Skipping {0} because it contains EL-expressions", resourceKey)); return; } if (packName != null) { resourceWriter.writePackedResource(packName, skin, resource); } else { resourceWriter.writeResource(skin, resource); } log.debug("rendered " + resourceKey + " (" + skin + ")"); } catch (Exception e) { log.debug("not rendered " + resourceKey + " (" + skin + ") - cought exception"); if (skin != null) { log.error( MessageFormat.format("Exception rendering resorce {0} using skin {1}: {2}", resourceKey, skin, e.getMessage()), e); } else { log.error(MessageFormat.format("Exception rendering resorce {0}: {1}", resourceKey, e.getMessage()), e); } } finally { faces.setSkin(null); faces.stopRequest(); } } private void checkResource() { log.debug("checking " + resourceKey); try { FacesContext facesContext = faces.startRequest(); faces.setSkin("DEFAULT"); Resource resource = createResource(facesContext, resourceKey); if (resource == null) { // TODO log null resource log.warn("null resource for resource key " + resourceKey + " (resource rendering will be skipped)"); skipped = true; return; } if (!filter.apply(resource)) { log.debug("filtered out resource: " + resourceKey + " (resource rendering will be skipped)"); log.debug(" - content-type: " + resource.getContentType() + ", qualifier: " + ResourceUtil.getResourceQualifier(resource)); skipped = true; return; } String contentType = resource.getContentType(); if (contentType == null) { // TODO log null content type log.warn("null content type for resource key " + resourceKey + " (resource rendering will be skipped)"); skipped = true; return; } skinDependent = ResourceSkinUtils.isSkinDependent(resource.getRequestPath()); } catch (Exception e) { throw (RuntimeException) e; } finally { log.debug("checked " + resourceKey + ": skinDependent: " + skinDependent + ", skipped: " + skipped); faces.setSkin(null); faces.stopRequest(); } } public Object call() throws Exception { checkResource(); if (skipped) { log.debug("Skipped resource rendering: " + resourceKey); } else { if (skinDependent) { for (String skin : skins) { if (shouldSkipResourceRenderingForSkin(skin)) { continue; } renderResource(skin); } } else { renderResource(null); } log.debug("Resource rendered: " + resourceKey); } return null; } private boolean shouldSkipResourceRenderingForSkin(String skin) { if ("plain".equals(skin)) { // detect whether the mime-type of the given resource path denotes image File resourceFileName = new File(resourceKey.getResourceName()); String mimeType = MimetypesFileTypeMap.getDefaultFileTypeMap().getContentType(resourceFileName); if (mimeType.startsWith("image/")) { log.debug(String.format("Skipped rendering of %s as it is image that isn't required by skin %s", resourceKey, skin)); return true; } } return false; } } private Logger log; private Faces faces; private ResourceWriter resourceWriter; private CompletionService<Object> completionService; private String[] skins = new String[0]; private Predicate<Resource> filter = Predicates.alwaysTrue(); private String packName; public ResourceTaskFactoryImpl(Faces faces, String packName) { super(); this.faces = faces; this.packName = packName; } private boolean containsELExpression(Resource resource) { InputStream is = null; try { is = resource.getInputStream(); byte[] bs = StreamUtils.toByteArray(is); for (int i = 0; i < bs.length; i++) { byte b = bs[i]; if (b == '#' && i + 1 < bs.length && bs[i + 1] == '{') { return true; } } } catch (IOException e) { log.error(e.getMessage(), e); } finally { try { is.close(); } catch(IOException e){ // Swallow } } return false; } private boolean shouldCheckForEL(Resource resource) { String resourceName = resource.getResourceName(); return resourceName.endsWith(".js") || resourceName.endsWith(".css"); } public void setLog(Logger log) { this.log = log; } public void setResourceWriter(ResourceWriter resourceWriter) { this.resourceWriter = resourceWriter; } public void setSkins(String[] skins) { this.skins = skins; } public void setCompletionService(CompletionService<Object> completionService) { this.completionService = completionService; } public void setFilter(Predicate<Resource> filter) { this.filter = filter; } public void submit(Iterable<ResourceKey> locators) { for (ResourceKey locator : locators) { completionService.submit(new ResourcesRendererCallable(locator)); } } }