Package org.exoplatform.portal.resource

Source Code of org.exoplatform.portal.resource.SkinService$SkinContext

/**
* Copyright (C) 2009 eXo Platform SAS.
*
* 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.exoplatform.portal.resource;

import org.exoplatform.commons.cache.future.FutureMap;
import org.exoplatform.commons.cache.future.Loader;
import org.exoplatform.commons.utils.BinaryOutput;
import org.exoplatform.commons.utils.ByteArrayOutput;
import org.exoplatform.commons.utils.PropertyManager;
import org.exoplatform.commons.utils.Safe;
import org.exoplatform.container.ExoContainerContext;
import org.exoplatform.management.annotations.Impact;
import org.exoplatform.management.annotations.ImpactType;
import org.exoplatform.management.annotations.Managed;
import org.exoplatform.management.annotations.ManagedDescription;
import org.exoplatform.management.annotations.ManagedName;
import org.exoplatform.management.jmx.annotations.NameTemplate;
import org.exoplatform.management.jmx.annotations.Property;
import org.exoplatform.management.rest.annotations.RESTEndpoint;
import org.exoplatform.portal.resource.compressor.ResourceCompressor;
import org.exoplatform.portal.resource.compressor.ResourceType;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.resources.Orientation;
import org.exoplatform.web.ControllerContext;
import org.exoplatform.web.WebAppController;
import org.exoplatform.web.controller.QualifiedName;
import org.exoplatform.web.controller.router.URIWriter;
import org.exoplatform.web.url.MimeType;
import org.gatein.portal.controller.resource.ResourceRequestHandler;
import org.gatein.wci.ServletContainerFactory;
import org.gatein.wci.WebAppListener;
import org.picocontainer.Startable;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.ServletContext;

@Managed
@NameTemplate({@Property(key = "view", value = "portal"), @Property(key = "service", value = "management"),
   @Property(key = "type", value = "skin")})
@ManagedDescription("Skin service")
@RESTEndpoint(path = "skinservice")
public class SkinService extends AbstractResourceService implements Startable
{

   protected static Log log = ExoLogger.getLogger("portal.SkinService");

   private static final String LEFT_P = "\\(";

   private static final String RIGHT_P = "\\)";

   /** Immutable and therefore thread safe. */
   private static final Pattern IMPORT_PATTERN =
      Pattern.compile("(@import\\s+" + "url" + LEFT_P + "['\"]?" + ")([^'\";]+.css)(" + "['\"]?" + RIGHT_P + "\\s*;)");

   /** Immutable and therefore thread safe. */
   private static final Pattern BACKGROUND_PATTERN =
      Pattern.compile("(background[^;]+url" + LEFT_P + "['\"]?" + ")([^'\";]+)(" + "['\"]?" + RIGHT_P + "[^;]*;)")

   /** Immutable and therefore thread safe. */
   private static final Pattern LT = Pattern.compile("[^{;]*;\\s*/\\*\\s*orientation=lt\\s*\\*/");

   /** Immutable and therefore thread safe. */
   private static final Pattern RT = Pattern.compile("[^{;]*;\\s*/\\*\\s*orientation=rt\\s*\\*/");

   public static final String DEFAULT_SKIN = "Default";

   /** The deployer. */
   private final WebAppListener deployer;

   /** The removal. */
   private final WebAppListener removal;

   private final Map<SkinKey, SkinConfig> portalSkins_;

   private final Map<SkinKey, SkinConfig> skinConfigs_;

   private final HashSet<String> availableSkins_;

   private final FutureMap<String, CachedStylesheet, SkinContext> ltCache;

   private final FutureMap<String, CachedStylesheet, SkinContext> rtCache;

   private final Map<String, Set<String>> portletThemes_;

   /**
    * The name of the portal container
    */
   final String portalContainerName;

   /**
    * An id used for caching request. The id life cycle is the same than the
    * class instance because we consider css will change until server is
    * restarted. Of course this only applies for the developing mode set to
    * false.
    */
   final String id = Long.toString(System.currentTimeMillis());
  
   private static final long MAX_AGE;
  
   static
   {
      long seconds = 86400;
      String propValue = PropertyManager.getProperty("gatein.assets.css.max-age");
      if (propValue != null)
      {
         try
         {
            seconds = Long.valueOf(propValue);
         }
         catch (NumberFormatException e)
         {
            log.warn("The gatein.assets.css.max-age property is not set properly.");
         }
      }
     
      MAX_AGE = seconds;
   }

   static class SkinContext
   {
      final ControllerContext controller;
      final Orientation orientation;
      SkinContext(ControllerContext controller, Orientation orientation)
      {
         this.controller = controller;
         this.orientation = orientation;
      }
   }

   public SkinService(ExoContainerContext context, ResourceCompressor compressor)
   {
      super(compressor);
      Loader<String, CachedStylesheet, SkinContext> loader = new Loader<String, CachedStylesheet, SkinContext>()
      {
         public CachedStylesheet retrieve(SkinContext context, String key) throws Exception
         {
            Resource skin = getCSSResource(key, key);
            if (skin == null)
            {
               return null;
            }
           
            StringBuffer sb = new StringBuffer();
            processCSSRecursively(context.controller, sb, true, skin, context.orientation);
            String css = sb.toString();
            try
            {
               if (SkinService.this.compressor.isSupported(ResourceType.STYLESHEET))
               {
                  css = SkinService.this.compressor.compress(css, ResourceType.STYLESHEET);
               }
            }
            catch (Exception e)
            {
               log.warn("Error when compressing CSS " + key + " for orientation " + context + " will use normal CSS instead", e);
            }

            return new CachedStylesheet(css);
         }
      };

      //
      portalSkins_ = new LinkedHashMap<SkinKey, SkinConfig>();
      skinConfigs_ = new LinkedHashMap<SkinKey, SkinConfig>(20);
      availableSkins_ = new HashSet<String>(5);
      ltCache = new FutureMap<String, CachedStylesheet, SkinContext>(loader);
      rtCache = new FutureMap<String, CachedStylesheet, SkinContext>(loader);
      portletThemes_ = new HashMap<String, Set<String>>();
      portalContainerName = context.getPortalContainerName();
      deployer = new GateInSkinConfigDeployer(portalContainerName, this);
      removal = new GateInSkinConfigRemoval(this);

      addResourceResolver(new CompositeResourceResolver(portalContainerName, skinConfigs_));
   }

   /**
    * Add a new category for portlet themes if it does not exist
    *
    * @param categoryName the category name
    */
   public void addCategoryTheme(String categoryName)
   {
      if (!portletThemes_.containsKey(categoryName))
      {
         portletThemes_.put(categoryName, new HashSet<String>());
      }
   }

   /**
    * @deprecated use {@link #addPortalSkin(String, String, String)} instead
    */
   @Deprecated
   public void addPortalSkin(String module, String skinName, String cssPath, ServletContext scontext)
   {
      addPortalSkin(module, skinName, cssPath);
   }
  
   /**
    * Add a portal skin with the <code>priority</code> is Integer.MAX_VALUE
    * and the <code>overwrite</code> is false by default
    *
    * @param module
    * @param skinName
    * @param cssPath
    */
   public void addPortalSkin(String module, String skinName, String cssPath)
   {
      addPortalSkin(module, skinName, cssPath, Integer.MAX_VALUE, false);
   }

   /**
    * @deprecated use {@link #addPortalSkin(String, String, String, boolean)} instead
    */
   @Deprecated
   public void addPortalSkin(String module, String skinName, String cssPath, ServletContext scontext, boolean overwrite)
   {
      addPortalSkin(module, skinName, cssPath, overwrite);
   }

   /**
    * Add a portal skin with the <code>priority</code> is Integer.MAX_VALUE by default
    *
    * @param module
    * @param skinName
    * @param cssPath
    * @param overwrite
    */
   public void addPortalSkin(String module, String skinName, String cssPath, boolean overwrite)
   {
      addPortalSkin(module, skinName, cssPath, Integer.MAX_VALUE, overwrite);
   }

   /**
    * Register a portal skin
    *
    * @param module
    *           skin module identifier
    * @param skinName
    *           skin name
    * @param cssPath
    *           path uri to the css file. This is relative to the root context,
    *           use leading '/'
    * @param overwrite
    *           if any previous skin should be replaced by that one
    * @param cssPriority         
    *           priority to support sorting in skin list
    */
   public void addPortalSkin(String module, String skinName, String cssPath, int priority, boolean overwrite)
   {
      availableSkins_.add(skinName);
      SkinKey key = new SkinKey(module, skinName);
      SkinConfig skinConfig = portalSkins_.get(key);
      if (skinConfig == null || overwrite)
      {
         if (priority < 0)
         {
            priority = Integer.MAX_VALUE;
         }
        
         skinConfig = new SimpleSkin(this, module, skinName, cssPath, priority);
         portalSkins_.put(key, skinConfig);
        
         if (log.isDebugEnabled())
         {
            log.debug("Adding Portal skin : Bind " + key + " to " + skinConfig);
         }
      }
   }
  
   /**
    * Register a portal skin with the specific <code>cssData</code>
    *
    * @deprecated This method is not supported anymore.
    *             The resource resolver pluggability mechanism should be used somehow
    * @param module
    *           skin module identifier
    * @param skinName
    *           skin name
    * @param cssPath
    *           path uri to the css file. This is relative to the root context,
    *           use leading '/'
    * @param cssData
    *           the content of css
    */
   @Deprecated
   public void addPortalSkin(String module, String skinName, String cssPath, String cssData)
   {
      throw new UnsupportedOperationException("This method is not supported anymore.");
   }
  
   /**
    * @deprecated use {@link #addSkin(String, String, String)} instead
    */
   @Deprecated
   public void addSkin(String module, String skinName, String cssPath, ServletContext scontext)
   {
      addSkin(module, skinName, cssPath);
   }
  
   /**
    * Add a skin with the <code>priority</code> is Integer.MAX_VALUE
    * and the <code>overwrite</code> is false by default
    *
    * @param module
    * @param skinName
    * @param cssPath
    */
   public void addSkin(String module, String skinName, String cssPath)
   {
      addSkin(module, skinName, cssPath, Integer.MAX_VALUE, false);
   }

   /**
    * @deprecated use {@link #addSkin(String, String, String, boolean)} instead
    */
   @Deprecated
   public void addSkin(String module, String skinName, String cssPath, ServletContext scontext, boolean overwrite)
   {
      addSkin(module, skinName, cssPath, overwrite);
   }
  
   /**
    * Add a portal skin with the <code>priority</code> is Integer.MAX_VALUE
    *
    * @param module
    * @param skinName
    * @param cssPath
    * @param overwrite
    */
   public void addSkin(String module, String skinName, String cssPath, boolean overwrite)
   {
      addSkin(module, skinName, cssPath, Integer.MAX_VALUE, overwrite);
   }

   /**
    *
    * Register the Skin for available portal Skins. Support priority
    *
    * @param module
    *           skin module identifier
    * @param skinName
    *           skin name
    * @param cssPath
    *           path uri to the css file. This is relative to the root context,
    *           use leading '/'
    * @param overwrite
    *           if any previous skin should be replaced by that one
    * @param priority
    *           priority to support sorting in skin list
    */
   public void addSkin(String module, String skinName, String cssPath, int priority, boolean overwrite)
   {
      availableSkins_.add(skinName);
      SkinKey key = new SkinKey(module, skinName);
      SkinConfig skinConfig = skinConfigs_.get(key);
      if (skinConfig == null || overwrite)
      {
         if (priority < 0)
         {
            priority = Integer.MAX_VALUE;
         }
        
         skinConfig = new SimpleSkin(this, module, skinName, cssPath, priority);
         skinConfigs_.put(key, skinConfig);

         if (log.isDebugEnabled())
         {
            log.debug("Adding skin : Bind " + key + " to " + skinConfig);
         }
      }
   }

   /**
    *
    * Register the Skin for available portal Skins. Do not replace existed Skin
    *
    * @param module
    *           skin module identifier
    * @param skinName
    *           skin name
    * @param cssPath
    *           path uri to the css file. This is relative to the root context,
    *           use leading '/'
    * @param scontext
    *           the webapp's {@link javax.servlet.ServletContext}
    */
   @Deprecated
   public void addSkin(String module, String skinName, String cssPath, String cssData)
   {
      availableSkins_.add(skinName);
      SkinKey key = new SkinKey(module, skinName);
      SkinConfig skinConfig = skinConfigs_.get(key);
      if (skinConfig == null)
      {
         skinConfigs_.put(key, new SimpleSkin(this, module, skinName, cssPath));
      }
      ltCache.remove(cssPath);
      rtCache.remove(cssPath);
   }
  
   /**
    * Merge several skins into one single skin.
    *
    * @param skins
    *           the skins to merge
    * @return the merged skin
    */
   public Skin merge(Collection<SkinConfig> skins)
   {
      return new CompositeSkin(this, skins);
   }

   /**
    * Registry theme category with its themes for portlet Theme
    * @param categoryName
    *             category name that will be registried
    * @param themesName
    *             list theme  name of categoryName
    */
   public void addTheme(String categoryName, List<String> themesName)
   {
      if (!portletThemes_.containsKey(categoryName))
         portletThemes_.put(categoryName, new HashSet<String>());
      Set<String> catThemes = portletThemes_.get(categoryName);
      for (String theme : themesName)
         catThemes.add(theme);
   }

   /**
    * Get names of all the currently registered skins.
    *
    * @return an unmodifiable Set of the currently registered skins
    */
   public Set<String> getAvailableSkinNames()
   {
      return availableSkins_;
   }

   /**
    * Return the CSS content of the file specified by the given URI.
    *
    *
    *
    * @param context
    * @param compress
    * @return the css contet or null if not found.
    */
   public String getCSS(ControllerContext context, boolean compress)
   {
      try
      {
         final ByteArrayOutput output = new ByteArrayOutput();
         boolean success = renderCSS(context, new ResourceRenderer()
         {
            public BinaryOutput getOutput() throws IOException
            {
               return output;
            }
            public void setExpiration(long seconds)
            {
            }
         }, compress);
        
         if (success)
         {
            return output.getString();
         }
         else
         {
            return null;
         }
      }
      catch (IOException e)
      {
//         log.error("Error while rendering css " + path, e);
         return null;
      }
      catch (RenderingException e)
      {
//         log.error("Error while rendering css " + path, e);
         return null;
      }
   }

   /**
    * Render css content of the file specified by the given URI
    *
    *
    * @param context
    * @param renderer
    *          the webapp's {@link org.exoplatform.portal.resource.ResourceRenderer}
    * @param path
    *          path uri to the css file
    * @param compress
    * @throws RenderingException
    * @throws IOException
    * @return <code>true</code> if the <code>CSS resource </code>is found and rendered;
    *         <code>false</code> otherwise.
    */
   public boolean renderCSS(
      ControllerContext context,
      ResourceRenderer renderer,
      boolean compress) throws RenderingException, IOException
   {
      String dir = context.getParameter(ResourceRequestHandler.ORIENTATION_QN);

      Orientation orientation = "rt".equals(dir) ? Orientation.RT : Orientation.LT;

      // Check if it is running under developing mode
      String resource = "/" + context.getParameter(ResourceRequestHandler.RESOURCE_QN) + ".css";
      if (!compress)
      {
         StringBuffer sb = new StringBuffer();
         Resource skin = getCSSResource(resource, resource);
         if (skin != null)
         {
            processCSSRecursively(context, sb, false, skin, orientation);
            byte[] bytes = sb.toString().getBytes("UTF-8");
            renderer.getOutput().write(bytes);
            return true;
         }
      }
      else
      {
         FutureMap<String, CachedStylesheet, SkinContext> cache = orientation == Orientation.LT ? ltCache : rtCache;
         CachedStylesheet cachedCss = cache.get(new SkinContext(context, orientation), resource);
         if (cachedCss != null)
         {
            renderer.setExpiration(MAX_AGE);
            cachedCss.writeTo(renderer.getOutput());
            return true;
         }
      }

      //
      return false;
   }

   /**
    * Return CSS data corresponding to the <code>path</code>
    *
    *
    * @param context
    * @param path
    *          path uri to the css file
    * @return css content of URI file or null if not found
    */
   @Deprecated
   public String getMergedCSS(ControllerContext context, String path)
   {
      return getCSS(context, true);
   }

   /**
    * Return a collection of Portal Skins that its elements are ordered by CSS priority
    *
    * @param skinName
    *          name of Portal Skin
    * @return
    *       all org.exoplatform.portal.resource.SkinConfig of Portal Skin
    */
   public Collection<SkinConfig> getPortalSkins(String skinName)
   {
      Set<SkinKey> keys = portalSkins_.keySet();
      List<SkinConfig> portalSkins = new ArrayList<SkinConfig>();
      for (SkinKey key : keys)
      {
         if (key.getName().equals(skinName))
            portalSkins.add(portalSkins_.get(key));
      }
      Collections.sort(portalSkins, new Comparator<SkinConfig>()
      {
         public int compare(SkinConfig o1, SkinConfig o2)
         {
            return o1.getCSSPriority() - o2.getCSSPriority();
         }
      });
      return portalSkins;
   }

   /**
    * Return the map of portlet themes
    *
    * @return the map of portlet themes
    */
   public Map<String, Set<String>> getPortletThemes()
   {
      return portletThemes_;
   }

   /**
    * Return a SkinConfig mapping by the module and skin name
    *
    * @param module
    * @param skinName
    * @return SkinConfig by SkinKey(module, skinName), or SkinConfig by SkinKey(module, SkinService.DEFAULT_SKIN)
    */
   public SkinConfig getSkin(String module, String skinName)
   {
      SkinConfig config = skinConfigs_.get(new SkinKey(module, skinName));
      if (config == null)
         skinConfigs_.get(new SkinKey(module, SkinService.DEFAULT_SKIN));
      return config;
   }

   /**
    * Remove SkinKey from SkinCache by portalName and skinName
    *
    * @deprecated the method name is wrong to the behaviour it does.
    *             Use {@link #removeSkin(String, String)} instead
    *
    * @param portalName
    * @param skinName
    */
   @Deprecated
   public void invalidatePortalSkinCache(String portalName, String skinName)
   {
      SkinKey key = new SkinKey(portalName, skinName);
      skinConfigs_.remove(key);
   }
  
   /**
    * Invalidate skin from the cache
    *
    * @param path the key
    */
   public void invalidateCachedSkin(String path)
   {
      ltCache.remove(path);
      rtCache.remove(path);
   }

   /**
    * Returns last modified date of cached css.
    * <p>
    * In development mode, it always returns {@link Long#MAX_VALUE}.
    * Return null if cached css can not be found
    * @param context
    * @param path - path must not be null
    */
   public long getLastModified(ControllerContext context)
   {
      String resource = "/" + context.getParameter(ResourceRequestHandler.RESOURCE_QN) + ".css";
     
      if (PropertyManager.isDevelopping())
      {
         return Long.MAX_VALUE;
      }

      FutureMap<String, CachedStylesheet, SkinContext> cache = ltCache;
      Orientation orientation = Orientation.LT;
      String dir = context.getParameter(ResourceRequestHandler.ORIENTATION_QN);
      if ("rt".equals(dir))
      {
         orientation = Orientation.RT;
         cache = rtCache;
      }

      CachedStylesheet cachedCSS = cache.get(new SkinContext(context, orientation), resource);
      if (cachedCSS == null)
      {
         return Long.MAX_VALUE;
      }
      else
      {
         return cachedCSS.getLastModified();
      }
   }

   /**
    * @deprecated The method name is not clear.
    *             Using {@link #removeSkin(String, String)} instead
    */
   @Deprecated
   public void remove(String module, String skinName)
   {
      removeSkin(module, skinName);
   }
  
   /**
    * Remove a Skin from the service as well as its cache
    *
    * @param module
    * @param skinName
    */
   public void removeSkin(String module, String skinName)
   {
      SkinKey key;
      if (skinName.length() == 0)
      {
         key = new SkinKey(module, DEFAULT_SKIN);
      }
      else
      {
         key = new SkinKey(module, skinName);
      }
     
      removeSkin(key);
   }
  
   /**
    * Remove a Skin mapped to the <code>key</code>
    *
    * @param key key whose mapping skin is to be removed from the service
    */
   public void removeSkin(SkinKey key)
   {
      if (key == null)
      {
         return;
      }
     
      SkinConfig remove = skinConfigs_.remove(key);
     
      if (remove != null)
      {
         invalidateCachedSkin(remove.getCSSPath());
      }
   }
  
   /**
    * @deprecated This is deprecated as its name was not clear.
    *             Use {@link #removeSkins(List)} instead
    */
   @Deprecated
   public void remove(List<SkinKey> keys) throws Exception
   {
      removeSkins(keys);
   }
  
   /**
    * Remove SkinConfig from Portal Skin Config by SkinKey
    * @param keys
    *          SkinKey list these will be removed
    * @throws Exception
    */
   public void removeSkins(List<SkinKey> keys) throws Exception
   {
      if (keys == null)
      {
         return;
      }
     
      for (SkinKey key : keys)
      {
         removeSkin(key);
      }
   }


   /**
    * Remove Skin from Portal available Skin by skin name
    * @param skinName
    *          name of skin that will be removed
    * @throws Exception
    */
   public void removeSupportedSkin(String skinName) throws Exception
   {
      availableSkins_.remove(skinName);
   }

   /**
    * Return the number of skin config maintaining in this SkinService
    *
    * @return the number of skin config maintaining in this SkinService
    */
   public int size()
   {
      return skinConfigs_.size();
   }
  
   /**
    *
    * This method delegates the resource resolving to MainResourceResolver and prints out appropriated log messages
    *
    * Consider the two cases the method is invoked
    *
    * Case 1: Resolve nested .css file
    *
    *  In Stylesheet.css we have the statement
    *
    *  @import url(xyzt.css);
    *
    *  To resolve the resource from xyzt.css, getCSSResource("xyzt.css", "Stylesheet.css") is called
    * 
    * Case 2: Resolve top root .css file
    *
    *  To resolve a top root Stylesheet.css file, getCSSResource("Stylesheet.css", "Stylesheet.css") is called
    *
    * @param cssPath
    * @param outerCssFile
    * @return
    *
    */
   private Resource getCSSResource(String cssPath, String outerCssFile)
   {
      Resource resource = mainResolver.resolve(cssPath);
      if (resource == null && log.isErrorEnabled())
      {
         String logMessage;
         if (!cssPath.equals(outerCssFile))
         {
            int lastIndexOfSlash = cssPath.lastIndexOf('/');
            String loadedCssFile = (lastIndexOfSlash >= 0)?(cssPath.substring(lastIndexOfSlash + 1)) : cssPath;
            logMessage =
               "Invalid <CSS FILE> configuration, please check the @import url(" + loadedCssFile + ") in "
                  + outerCssFile + " , SkinService could not load the skin " + cssPath;
         }
         else
         {
            logMessage =
               "Not found <CSS FILE>, the path " + cssPath + " is invalid, SkinService could not load the skin "
                  + cssPath;
         }
         log.error(logMessage);
      }
      return resource;
   }

   /**
    * Apply CSS for Skin <br/>
    * If skin is null, do nothing
    *
    * @param context
    * @param appendable
    * @param merge
    * @param skin
    * @param orientation
    * @throws RenderingException
    * @throws IOException
    */
   private void processCSSRecursively(ControllerContext context, Appendable appendable, boolean merge, Resource skin, Orientation orientation)
      throws RenderingException, IOException
   {
      if(skin == null)
      {
         return;
      }
      // The root URL for the entry
      String basePath = skin.getContextPath() + skin.getParentPath();

      //
      Reader tmp = skin.read();
      if (tmp == null)
      {
         throw new RenderingException("No skin resolved for path " + skin.getResourcePath());
      }
      BufferedReader reader = new SkipCommentReader(tmp, new CommentBlockHandler.OrientationCommentBlockHandler());
      try
      {
         String line = reader.readLine();
         while (line != null)
         {           
            line = proccessOrientation(line, orientation);           
            line = proccessBackgroundUrl(line, basePath);                      
           
            Matcher matcher = IMPORT_PATTERN.matcher(line);
            while (matcher.find())
            {
               String includedPath = matcher.group(2);              
               if (!includedPath.startsWith("/"))
               {
                  includedPath = basePath + includedPath;
               }
              
               StringBuffer strReplace = new StringBuffer();
               if (merge)
               {                 
                  Resource ssskin = getCSSResource(includedPath, basePath + skin.getFileName());
                  processCSSRecursively(context, strReplace, merge, ssskin, orientation);
               }
               else
               {
                  // Remove leading '/' and trailing '.css'
                  String resource = includedPath.substring(1, includedPath.length() - ".css".length());

                  //
                  Map<QualifiedName, String> params = new HashMap<QualifiedName, String>();
                  params.put(ResourceRequestHandler.VERSION_QN, ResourceRequestHandler.VERSION);
                  params.put(ResourceRequestHandler.ORIENTATION_QN, orientation == Orientation.RT ? "rt" : "lt");
                  params.put(ResourceRequestHandler.COMPRESS_QN, merge ? "min" : "");
                  params.put(WebAppController.HANDLER_PARAM, "skin");
                  params.put(ResourceRequestHandler.RESOURCE_QN, resource);
                  StringBuilder embeddedPath = new StringBuilder();
                  context.renderURL(params, new URIWriter(embeddedPath, MimeType.PLAIN));

                  //
                  strReplace.append(matcher.group(1));
                  strReplace.append(embeddedPath);
                  strReplace.append(matcher.group(3));
               }              
               String str = strReplace.toString().replaceAll("\\$", "\\\\\\$");
               matcher.appendReplacement((StringBuffer)appendable, str);
            }
            matcher.appendTail((StringBuffer)appendable);
           
            if ((line = reader.readLine()) != null)
            {
               appendable.append("\n");
            }
         }        
      }
      finally
      {
         Safe.close(reader);
      }
   }

   private String proccessBackgroundUrl(String line, String basePath)
   {
      // Rewrite background url pattern
      Matcher matcher = BACKGROUND_PATTERN.matcher(line);
     
      StringBuffer tmpBuilder = new StringBuffer();
      while (matcher.find())
      {
         if (!matcher.group(2).startsWith("\"/")) {
            if (!matcher.group(2).startsWith("'/")) {
               if (!matcher.group(2).startsWith("/")) {
                  StringBuilder strReplace = new StringBuilder();
                  strReplace.append(matcher.group(1));
                  strReplace.append(basePath);
                  strReplace.append(matcher.group(2));
                  strReplace.append(matcher.group(3));
                 
                  matcher.appendReplacement(tmpBuilder, strReplace.toString());         
               }            
            }
         }                 
      }
      matcher.appendTail(tmpBuilder);
      return tmpBuilder.toString();
   }

   private String proccessOrientation(String line, Orientation orientation)
   {
      Pattern orientationPattern = orientation == Orientation.LT ? RT : LT;
      Matcher matcher = orientationPattern.matcher(line);
      StringBuffer tmpBuilder = new StringBuffer();
      while (matcher.find())
      {
         matcher.appendReplacement(tmpBuilder, "");
      }
      matcher.appendTail(tmpBuilder);
      return tmpBuilder.toString();
   }

   /**
    * Get all available skin
    * @return all available skin
    *   
    */
   @Managed
   @ManagedDescription("The list of registered skins identifiers")
   public String[] getSkinList()
   {
      // get all available skin
      List<String> availableSkin = new ArrayList<String>();
      for (String skin : availableSkins_)
      {
         availableSkin.add(skin);
      }
      // sort skin name asc
      Collections.sort(availableSkin);

      return availableSkin.toArray(new String[availableSkin.size()]);
   }

   /**
    * Clean cache, reload all Skins
    */
   @Managed
   @ManagedDescription("Reload all skins")
   @Impact(ImpactType.WRITE)
   public void reloadSkins()
   {
      // remove all ltCache, rtCache
      ltCache.clear();
      rtCache.clear();
   }

   /**
    * reload skin by skin ID
    * @param skinId
    *          the skin ID that will be reloaded
    */
   @Managed
   @ManagedDescription("Reload a specified skin")
   public void reloadSkin(@ManagedDescription("The skin id") @ManagedName("skinId") String skinId)
   {
      ltCache.remove(skinId);
      rtCache.remove(skinId);
   }

   /**
    * Start service.
    * Registry org.exoplatform.portal.resource.GateInSkinConfigDeployer and org.exoplatform.portal.resource.GateInSkinConfigRemoval into ServletContainer.
    * @see org.picocontainer.Startable#start()
    */
   public void start()
   {
      ServletContainerFactory.getServletContainer().addWebAppListener(deployer);
      ServletContainerFactory.getServletContainer().addWebAppListener(removal);
   }

   /**
    * Stop service
    * Remove org.exoplatform.portal.resource.GateInSkinConfigDeployer and org.exoplatform.portal.resource.GateInSkinConfigRemoval from ServletContainer.
    * @see org.picocontainer.Startable#stop()
    */
   public void stop()
   {
      ServletContainerFactory.getServletContainer().removeWebAppListener(deployer);
      ServletContainerFactory.getServletContainer().removeWebAppListener(removal);
   }
}
TOP

Related Classes of org.exoplatform.portal.resource.SkinService$SkinContext

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.