/*
* Copyright (c) 2010-2017, b3log.org & hacpai.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.b3log.solo.util;
import freemarker.template.TemplateExceptionHandler;
import org.b3log.latke.Keys;
import org.b3log.latke.Latkes;
import org.b3log.latke.ioc.LatkeBeanManager;
import org.b3log.latke.ioc.Lifecycle;
import org.b3log.latke.logging.Level;
import org.b3log.latke.logging.Logger;
import org.b3log.latke.service.LangPropsService;
import org.b3log.latke.service.LangPropsServiceImpl;
import org.b3log.latke.service.ServiceException;
import org.b3log.latke.util.Locales;
import org.b3log.latke.util.Requests;
import org.b3log.latke.util.Stopwatchs;
import org.b3log.latke.util.Strings;
import org.b3log.latke.util.freemarker.Templates;
import org.b3log.solo.SoloServletListener;
import org.b3log.solo.model.Skin;
import javax.servlet.ServletContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
/**
* Skin utilities.
*
* @author <a href="http://88250.b3log.org">Liang Ding</a>
* @version 1.1.5.8, May 7, 2017
* @since 0.3.1
*/
public final class Skins {
/**
* Logger.
*/
private static final Logger LOGGER = Logger.getLogger(Skins.class.getName());
/**
* Properties map.
*/
private static final Map<String, Map<String, String>> LANG_MAP = new HashMap<String, Map<String, String>>();
/**
* Private default constructor.
*/
private Skins() {
}
/**
* Fills the specified data model with the current skink's (WebRoot/skins/${skinName}/lang/lang_xx_XX.properties)
* and core language (WebRoot/WEB-INF/classes/lang_xx_XX.properties) configurations.
*
* @param localeString the specified locale string
* @param currentSkinDirName the specified current skin directory name
* @param dataModel the specified data model
* @throws ServiceException service exception
*/
public static void fillLangs(final String localeString, final String currentSkinDirName, final Map<String, Object> dataModel)
throws ServiceException {
Stopwatchs.start("Fill Skin Langs");
try {
final String langName = currentSkinDirName + "." + localeString;
Map<String, String> langs = LANG_MAP.get(langName);
if (null == langs) {
LANG_MAP.clear(); // Collect unused skin languages
LOGGER.log(Level.DEBUG, "Loading skin [dirName={0}, locale={1}]", currentSkinDirName, localeString);
langs = new HashMap<String, String>();
final String language = Locales.getLanguage(localeString);
final String country = Locales.getCountry(localeString);
final ServletContext servletContext = SoloServletListener.getServletContext();
final InputStream inputStream = servletContext.getResourceAsStream(
"/skins/" + currentSkinDirName + "/lang/lang_" + language + '_' + country + ".properties");
final Properties props = new Properties();
props.load(inputStream);
final Set<Object> keys = props.keySet();
for (final Object key : keys) {
langs.put((String) key, props.getProperty((String) key));
}
LANG_MAP.put(langName, langs);
LOGGER.log(Level.DEBUG, "Loaded skin[dirName={0}, locale={1}, keyCount={2}]",
currentSkinDirName, localeString, langs.size());
}
dataModel.putAll(langs); // Fills the current skin's language configurations
// Fills the core language configurations
final LatkeBeanManager beanManager = Lifecycle.getBeanManager();
final LangPropsService langPropsService = beanManager.getReference(LangPropsServiceImpl.class);
dataModel.putAll(langPropsService.getAll(Latkes.getLocale()));
} catch (final IOException e) {
LOGGER.log(Level.ERROR, "Fills skin langs failed", e);
throw new ServiceException(e);
} finally {
Stopwatchs.end();
}
}
/**
* Sets the directory for template loading with the specified skin directory name, and sets the directory for mobile
* request template loading.
*
* @param skinDirName the specified skin directory name
*/
public static void setDirectoryForTemplateLoading(final String skinDirName) {
final ServletContext servletContext = SoloServletListener.getServletContext();
Templates.MAIN_CFG.setServletContextForTemplateLoading(servletContext, "/skins/" + skinDirName);
Templates.MAIN_CFG.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
Templates.MAIN_CFG.setLogTemplateExceptions(false);
Templates.MOBILE_CFG.setServletContextForTemplateLoading(servletContext, "/skins/mobile");
Templates.MOBILE_CFG.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
Templates.MOBILE_CFG.setLogTemplateExceptions(false);
}
/**
* Gets all skin directory names. Scans the /skins/ directory, using the subdirectory of it as the skin directory
* name, for example,
* <pre>
* ${Web root}/skins/
* <b>default</b>/
* <b>mobile</b>/
* <b>classic</b>/
* </pre>.
*
* @return a set of skin name, returns an empty set if not found
*/
public static Set<String> getSkinDirNames() {
final ServletContext servletContext = SoloServletListener.getServletContext();
final Set<String> ret = new HashSet<String>();
@SuppressWarnings("unchecked") final Set<String> resourcePaths = servletContext.getResourcePaths("/skins");
for (final String path : resourcePaths) {
final String dirName = path.substring("/skins".length() + 1, path.length() - 1);
if (dirName.startsWith(".")) {
continue;
}
ret.add(dirName);
}
return ret;
}
/**
* Gets skin directory name from the specified request. Refers to https://github.com/b3log/solo/issues/12060 for
* more details.
*
* @param request the specified request
* @return directory name, or {@code "default"} if not found
*/
public static String getSkinDirName(final HttpServletRequest request) {
// https://github.com/b3log/solo/issues/12060
if (Requests.mobileRequest(request)) {
return (String) request.getAttribute(Keys.TEMAPLTE_DIR_NAME); // resolved in listener
}
// 1. Get skin from query
final String specifiedSkin = request.getParameter(Skin.SKIN);
if ("default".equals(specifiedSkin)) {
return "default";
}
if (!Strings.isEmptyOrNull(specifiedSkin)) {
final Set<String> skinDirNames = Skins.getSkinDirNames();
if (skinDirNames.contains(specifiedSkin)) {
return specifiedSkin;
} else {
return null;
}
}
// 2. Get skin from cookie
final Cookie[] cookies = request.getCookies();
if (null != cookies) {
for (final Cookie cookie : cookies) {
if (Skin.SKIN.equals(cookie.getName())) {
final String skin = cookie.getValue();
final Set<String> skinDirNames = Skins.getSkinDirNames();
if (skinDirNames.contains(skin)) {
return skin;
}
}
}
}
return "default";
}
}