001/* 002 * Copyright 2017 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.web.glyph; 017 018import java.io.InputStream; 019import java.util.ArrayList; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.HashMap; 023import java.util.LinkedHashSet; 024import java.util.List; 025import java.util.Map; 026import java.util.regex.Matcher; 027import java.util.regex.Pattern; 028 029import org.apache.avalon.framework.component.Component; 030import org.apache.avalon.framework.configuration.Configuration; 031import org.apache.avalon.framework.context.Context; 032import org.apache.avalon.framework.context.ContextException; 033import org.apache.avalon.framework.context.Contextualizable; 034import org.apache.avalon.framework.service.ServiceException; 035import org.apache.avalon.framework.service.ServiceManager; 036import org.apache.avalon.framework.service.Serviceable; 037import org.apache.cocoon.components.ContextHelper; 038import org.apache.cocoon.environment.Request; 039import org.apache.commons.lang.StringUtils; 040import org.apache.excalibur.source.Source; 041import org.apache.excalibur.source.SourceResolver; 042 043import org.ametys.core.ui.Callable; 044import org.ametys.plugins.core.ui.glyph.CssFontHelper; 045import org.ametys.runtime.plugin.PluginsManager; 046import org.ametys.runtime.plugin.component.AbstractLogEnabled; 047import org.ametys.web.repository.site.Site; 048import org.ametys.web.repository.site.SiteManager; 049import org.ametys.web.skin.Skin; 050import org.ametys.web.skin.SkinConfigurationHelper; 051import org.ametys.web.skin.SkinsManager; 052 053/** 054 * Manager for skin glyph sources 055 */ 056public class SkinGlyphSourceManager extends AbstractLogEnabled implements Serviceable, Component, Contextualizable 057{ 058 /** The Avalon Role */ 059 public static final String ROLE = SkinGlyphSourceManager.class.getName(); 060 061 private static final Pattern __SKIN_SOURCE_URI_PATTERN = Pattern.compile("^skin://resources/(.*)$"); 062 private static final Pattern __PLUGIN_SOURCE_URI_PATTERN = Pattern.compile("^plugin:(" + PluginsManager.PLUGIN_NAME_REGEXP + ")://resources/(.*)$"); 063 064 private CssFontHelper _cssFontHelper; 065 private SourceResolver _resolver; 066 private SiteManager _siteManager; 067 private Context _context; 068 069 private Map<String, Long> _lastUpdate = new HashMap<>(); 070 private Map<String, List<String>> _cssFiles = new HashMap<>(); 071 072 private SkinConfigurationHelper _skinConfigurationHelper; 073 074 private SkinsManager _skinsManager; 075 076 @Override 077 public void service(ServiceManager smanager) throws ServiceException 078 { 079 _cssFontHelper = (CssFontHelper) smanager.lookup(CssFontHelper.ROLE); 080 _siteManager = (SiteManager) smanager.lookup(SiteManager.ROLE); 081 _resolver = (SourceResolver) smanager.lookup(SourceResolver.ROLE); 082 _skinsManager = (SkinsManager) smanager.lookup(SkinsManager.ROLE); 083 _skinConfigurationHelper = (SkinConfigurationHelper) smanager.lookup(SkinConfigurationHelper.ROLE); 084 } 085 086 @Override 087 public void contextualize(Context context) throws ContextException 088 { 089 _context = context; 090 } 091 092 /** 093 * Determines if the skin has available glyphesIde 094 * @param siteName The site name 095 * @return true if the skin has available glyphes 096 * @throws Exception if failed to read skin files 097 */ 098 @Callable 099 public boolean hasGlyphs(String siteName) throws Exception 100 { 101 Site site = _siteManager.getSite(siteName); 102 String skinName = site.getSkinId(); 103 104 return !getGlyphs(skinName).isEmpty(); 105 } 106 107 /** 108 * Get the glyphs provided by the skin 109 * @param skinName The skin name 110 * @return The CSS class names for glyphs 111 * @throws Exception if failed to read skin files 112 */ 113 @Callable 114 public List<Map<String, List<String>>> getGlyphsStore(String skinName) throws Exception 115 { 116 List<Map<String, List<String>>> glyphs = new ArrayList<>(); 117 118 Collection<List<String>> glyphClassNames = getGlyphs(skinName); 119 for (List<String> similarGlyphClassNames : glyphClassNames) 120 { 121 Map<String, List<String>> glyph = new HashMap<>(); 122 glyph.put("cssClassNames", similarGlyphClassNames); 123 glyphs.add(glyph); 124 } 125 126 return glyphs; 127 } 128 129 /** 130 * Get the CSS class names for glyphes contained in the skin 131 * @param skinName The skin name 132 * @return The CSS class names for glyphes 133 * @throws Exception if failed to read skin files 134 */ 135 public Collection<List<String>> getGlyphs(String skinName) throws Exception 136 { 137 Collection<List<String>> glyphes = new LinkedHashSet<>(); 138 139 List<String> cssUrisWithFontFace = getCssUrisWithFontFace(skinName); 140 for (String cssUri : cssUrisWithFontFace) 141 { 142 glyphes.addAll(_cssFontHelper.getGlyphClassNames(cssUri, null)); 143 } 144 145 return glyphes; 146 } 147 148 /** 149 * Get the CSS files with 'font-face' rule in the skin 150 * @param siteName The site name 151 * @return The CSS files with font 152 * @throws Exception If failed to get the list of CSS files with 'font-face' rule 153 */ 154 @Callable 155 public Map<String, Object> getCSSFiles(String siteName) throws Exception 156 { 157 Request request = ContextHelper.getRequest(_context); 158 159 Site site = _siteManager.getSite(siteName); 160 String skinName = site.getSkinId(); 161 162 Map<String, Object> result = new HashMap<>(); 163 164 if (StringUtils.isNotBlank(skinName)) 165 { 166 List<String> cssUris = getCssUrisWithFontFace(skinName); 167 168 List<String> cssPaths = new ArrayList<>(); 169 for (String cssUri : cssUris) 170 { 171 Matcher m = __SKIN_SOURCE_URI_PATTERN.matcher(cssUri); 172 if (m.matches()) 173 { 174 cssPaths.add(request.getContextPath() + "/skins/" + skinName + "/resources/" + m.group(1)); 175 } 176 else 177 { 178 m = __PLUGIN_SOURCE_URI_PATTERN.matcher(cssUri); 179 if (m.matches()) 180 { 181 String pluginName = m.group(1); 182 String path = m.group(2); 183 184 cssPaths.add(request.getContextPath() + "/plugins/" + pluginName + "/resources/" + path); 185 } 186 } 187 } 188 result.put("cssFiles", cssPaths); 189 } 190 return result; 191 } 192 193 /** 194 * Get the URI of CSS files with the 'font-face' rule 195 * @param skinName The skin name 196 * @return the URI of CSS files with the 'font-face' rule 197 * @throws Exception if fails to read conf/glyph.xml file 198 */ 199 public List<String> getCssUrisWithFontFace(String skinName) throws Exception 200 { 201 List<String> cssURIs = _getCssFileURIs(skinName); 202 203 List<String> cssUrisWithFontFace = new ArrayList<>(); 204 for (String cssURI : cssURIs) 205 { 206 if (_cssFontHelper.hasFontFaceRule(cssURI)) 207 { 208 cssUrisWithFontFace.add(cssURI); 209 } 210 else 211 { 212 getLogger().warn("Font-face rule was not found in CSS style sheet '" + cssURI + "'. It will be ignored"); 213 } 214 } 215 216 return cssUrisWithFontFace; 217 } 218 219 /** 220 * Get the URI of CSS files listed in conf/fonts.xml file 221 * @param skinName The skin name 222 * @return The list of css files uri 223 * @throws Exception if fails to read conf/fonts.xml file 224 */ 225 private synchronized List<String> _getCssFileURIs(String skinName) throws Exception 226 { 227 Source src = null; 228 try 229 { 230 String confFileUri = "skin:" + skinName + "://conf/fonts.xml"; 231 232 src = _resolver.resolveURI(confFileUri); 233 234 if (src.exists()) 235 { 236 if (!_lastUpdate.containsKey(skinName) || _lastUpdate.get(skinName) < src.getLastModified()) 237 { 238 List<String> files = new ArrayList<>(); 239 240 Skin skin = _skinsManager.getSkin(skinName); 241 try (InputStream xslIs = this.getClass().getResourceAsStream("fonts-merge.xsl")) 242 { 243 Configuration configuration = _skinConfigurationHelper.getInheritanceMergedConfiguration(skin, "conf/fonts.xml", xslIs); 244 Configuration[] filesConfiguration = configuration.getChildren("file"); 245 246 for (Configuration fileConfiguration : filesConfiguration) 247 { 248 String pluginName = fileConfiguration.getAttribute("plugin", null); 249 String file = fileConfiguration.getValue(); 250 251 if (pluginName == null) 252 { 253 files.add("skin://resources/" + file); 254 } 255 else 256 { 257 files.add("plugin:" + pluginName + "://resources/" + file); 258 } 259 } 260 } 261 262 _cssFiles.put(skinName, files); 263 _lastUpdate.put(skinName, src.getLastModified()); 264 } 265 266 return _cssFiles.get(skinName); 267 } 268 } 269 finally 270 { 271 _resolver.release(src); 272 } 273 274 return Collections.EMPTY_LIST; 275 } 276}