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.Collections; 021import java.util.HashMap; 022import java.util.LinkedHashSet; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026 027import javax.xml.parsers.SAXParserFactory; 028 029import org.apache.avalon.framework.component.Component; 030import org.apache.avalon.framework.configuration.Configuration; 031import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; 032import org.apache.avalon.framework.context.Context; 033import org.apache.avalon.framework.context.ContextException; 034import org.apache.avalon.framework.context.Contextualizable; 035import org.apache.avalon.framework.service.ServiceException; 036import org.apache.avalon.framework.service.ServiceManager; 037import org.apache.avalon.framework.service.Serviceable; 038import org.apache.cocoon.components.ContextHelper; 039import org.apache.cocoon.environment.Request; 040import org.apache.commons.lang.StringUtils; 041import org.apache.excalibur.source.Source; 042import org.apache.excalibur.source.SourceResolver; 043import org.xml.sax.XMLReader; 044 045import org.ametys.core.ui.Callable; 046import org.ametys.plugins.core.ui.glyph.CssFontHelper; 047import org.ametys.runtime.plugin.component.AbstractLogEnabled; 048import org.ametys.web.repository.site.Site; 049import org.ametys.web.repository.site.SiteManager; 050 051/** 052 * Manager for skin glyph sources 053 */ 054public class SkinGlyphSourceManager extends AbstractLogEnabled implements Serviceable, Component, Contextualizable 055{ 056 /** The Avalon Role */ 057 public static final String ROLE = SkinGlyphSourceManager.class.getName(); 058 059 private CssFontHelper _cssFontHelper; 060 private SourceResolver _resolver; 061 private SiteManager _siteManager; 062 private Context _context; 063 064 private long _lastUpdate; 065 private Map<String, List<String>> _cssFiles = new HashMap<>(); 066 067 @Override 068 public void service(ServiceManager smanager) throws ServiceException 069 { 070 _cssFontHelper = (CssFontHelper) smanager.lookup(CssFontHelper.ROLE); 071 _siteManager = (SiteManager) smanager.lookup(SiteManager.ROLE); 072 _resolver = (SourceResolver) smanager.lookup(SourceResolver.ROLE); 073 } 074 075 @Override 076 public void contextualize(Context context) throws ContextException 077 { 078 _context = context; 079 } 080 081 /** 082 * Determines if the skin has available glyphesIde 083 * @param siteName The site name 084 * @return true if the skin has available glyphes 085 * @throws Exception if failed to read skin files 086 */ 087 @Callable 088 public boolean hasGlyphs(String siteName) throws Exception 089 { 090 Site site = _siteManager.getSite(siteName); 091 String skinName = site.getSkinId(); 092 093 return !getGlyphs(skinName).isEmpty(); 094 } 095 096 /** 097 * Get the glyphs provided by the skin 098 * @param skinName The skin name 099 * @return The CSS class names for glyphs 100 * @throws Exception if failed to read skin files 101 */ 102 @Callable 103 public List<Map<String, String>> getGlyphsStore(String skinName) throws Exception 104 { 105 List<Map<String, String>> glyphs = new ArrayList<>(); 106 107 Set<String> glyphClassNames = getGlyphs(skinName); 108 for (String glyphClassName : glyphClassNames) 109 { 110 Map<String, String> glyph = new HashMap<>(); 111 glyph.put("cssClassName", glyphClassName); 112 glyphs.add(glyph); 113 } 114 115 return glyphs; 116 } 117 118 /** 119 * Get the CSS class names for glyphes contained in the skin 120 * @param skinName The skin name 121 * @return The CSS class names for glyphes 122 * @throws Exception if failed to read skin files 123 */ 124 public Set<String> getGlyphs(String skinName) throws Exception 125 { 126 Set<String> glyphes = new LinkedHashSet<>(); 127 128 List<String> relativePathsWithFontFace = getRelativeCssFilesWithFontFace(skinName); 129 for (String relativePath : relativePathsWithFontFace) 130 { 131 String sourceUri = "skin://resources/" + relativePath; 132 glyphes.addAll(_cssFontHelper.getGlyphClassNames(sourceUri, null)); 133 } 134 135 return glyphes; 136 } 137 138 /** 139 * Get the CSS files with 'font-face' rule in the skin 140 * @param siteName The site name 141 * @return The CSS files with font 142 * @throws Exception If failed to get the list of CSS files with 'font-face' rule 143 */ 144 @Callable 145 public Map<String, Object> getCSSFiles(String siteName) throws Exception 146 { 147 Request request = ContextHelper.getRequest(_context); 148 149 Site site = _siteManager.getSite(siteName); 150 String skinName = site.getSkinId(); 151 152 String resourcePath = request.getContextPath() + "/skins/" + skinName + "/resources/"; 153 154 Map<String, Object> result = new HashMap<>(); 155 156 if (StringUtils.isNotBlank(skinName)) 157 { 158 List<String> relativePathsWithFontFace = getRelativeCssFilesWithFontFace(skinName); 159 160 List<String> cssPaths = new ArrayList<>(); 161 for (String relativePath : relativePathsWithFontFace) 162 { 163 cssPaths.add(resourcePath + relativePath); 164 } 165 result.put("cssFiles", cssPaths); 166 } 167 return result; 168 } 169 170 /** 171 * Get the URI of CSS files with the 'font-face' rule 172 * @param skinName The skin name 173 * @return the URI of CSS files with the 'font-face' rule 174 * @throws Exception if fails to read conf/glyph.xml file 175 */ 176 public List<String> getRelativeCssFilesWithFontFace(String skinName) throws Exception 177 { 178 List<String> relativeCssFiles = _getRelativeCssFiles(skinName); 179 180 List<String> relativeCssFilesWithFontFace = new ArrayList<>(); 181 for (String relativeCssFile : relativeCssFiles) 182 { 183 String cssURI = "skin://resources/" + relativeCssFile; 184 if (_cssFontHelper.hasFontFaceRule(cssURI)) 185 { 186 relativeCssFilesWithFontFace.add(relativeCssFile); 187 } 188 else 189 { 190 getLogger().warn("Font-face rule was not found in CSS style sheet '" + cssURI + "'. It will be ignored"); 191 } 192 } 193 194 return relativeCssFilesWithFontFace; 195 } 196 197 /** 198 * Get the URI of CSS files listed in conf/glyph.xml file 199 * @param skinName The skin name 200 * @return The list of css files 201 * @throws Exception if fails to read conf/glyph.xml file 202 */ 203 private List<String> _getRelativeCssFiles(String skinName) throws Exception 204 { 205 Source src = null; 206 try 207 { 208 SAXParserFactory factory = SAXParserFactory.newInstance(); 209 210 XMLReader reader = factory.newSAXParser().getXMLReader(); 211 212 DefaultConfigurationBuilder confBuilder = new DefaultConfigurationBuilder(reader); 213 214 String confFileUri = "context://skins/" + skinName + "/conf/fonts.xml"; 215 216 src = _resolver.resolveURI(confFileUri); 217 218 if (src.exists()) 219 { 220 if (_lastUpdate < src.getLastModified()) 221 { 222 List<String> files = new ArrayList<>(); 223 224 try (InputStream is = src.getInputStream()) 225 { 226 227 Configuration configuration = confBuilder.build(is, src.getURI()); 228 Configuration[] filesConfiguration = configuration.getChildren("file"); 229 230 for (Configuration fileConfiguration : filesConfiguration) 231 { 232 String file = fileConfiguration.getValue(); 233 files.add(file); 234 } 235 } 236 237 _cssFiles.put(skinName, files); 238 _lastUpdate = src.getLastModified(); 239 } 240 241 return _cssFiles.get(skinName); 242 } 243 } 244 finally 245 { 246 _resolver.release(src); 247 } 248 249 return Collections.EMPTY_LIST; 250 } 251}