/*
* Copyright (c) 2002-2012 Alibaba Group Holding Limited.
* All rights reserved.
*
* 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 com.alibaba.citrus.service.template.impl;
import static com.alibaba.citrus.util.Assert.*;
import static com.alibaba.citrus.util.CollectionUtil.*;
import static com.alibaba.citrus.util.FileUtil.*;
import static com.alibaba.citrus.util.StringUtil.*;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.alibaba.citrus.service.AbstractService;
import com.alibaba.citrus.service.configuration.ProductionModeAware;
import com.alibaba.citrus.service.template.TemplateContext;
import com.alibaba.citrus.service.template.TemplateEngine;
import com.alibaba.citrus.service.template.TemplateException;
import com.alibaba.citrus.service.template.TemplateNotFoundException;
import com.alibaba.citrus.service.template.TemplateService;
/**
* 实现<code>TemplateService</code>。
*
* @author Michael Zhou
*/
public class TemplateServiceImpl extends AbstractService<TemplateService> implements TemplateService,
ProductionModeAware {
private Map<String, TemplateEngine> engines; // engineName -> engine
private Map<String, TemplateEngine> engineMappings; // ext -> engine
private Map<String, String> engineNameMappings; // ext -> engineName
private String defaultExtension;
private boolean searchExtensions;
private boolean searchLocalizedTemplates;
private TemplateSearchingStrategy[] strategies;
private Boolean cacheEnabled;
private boolean productionMode = true;
private Map<TemplateKey, TemplateMatchResult> matchedTemplates;
public void setEngines(Map<String, TemplateEngine> engines) {
this.engines = engines;
}
public void setEngineNameMappings(Map<String, String> engineNameMappings) {
this.engineNameMappings = engineNameMappings;
}
public void setDefaultExtension(String defaultExtension) {
this.defaultExtension = defaultExtension;
}
public void setSearchExtensions(boolean searchExtensions) {
this.searchExtensions = searchExtensions;
}
public void setSearchLocalizedTemplates(boolean searchLocalizedTemplates) {
this.searchLocalizedTemplates = searchLocalizedTemplates;
}
public Boolean isCacheEnabled() {
return cacheEnabled;
}
public void setCacheEnabled(Boolean cacheEnabled) {
this.cacheEnabled = cacheEnabled;
}
public boolean isProductionMode() {
return productionMode;
}
public void setProductionMode(boolean productionMode) {
this.productionMode = productionMode;
}
/** 初始化service。 */
@Override
protected void init() {
if (engines == null) {
engines = createHashMap();
}
if (engines.isEmpty()) {
getLogger().warn("No Template Engine registered for TemplateService: id={}", getBeanName());
}
if (cacheEnabled == null) {
cacheEnabled = productionMode; // 如果未指定cacheEnabled,则默认当productionMode时,打开cache。
}
if (cacheEnabled) {
matchedTemplates = createConcurrentHashMap();
}
Set<String> remappedNames = createHashSet();
engineMappings = createTreeMap();
// 生成engineMappings
if (engineNameMappings != null) {
for (Map.Entry<String, String> entry : engineNameMappings.entrySet()) {
String ext = entry.getKey();
String engineName = entry.getValue();
assertTrue(!isEmpty(ext) && !ext.startsWith("."), "Invalid extension: %s", ext);
assertTrue(engines.containsKey(engineName), "TemplateEngine \"%s\" not defined. Defined names: %s",
engineName, engines.keySet());
remappedNames.add(engineName);
engineMappings.put(ext, engines.get(engineName));
getLogger().debug("Template Name \"*.{}\" mapped to Template Engine: {}", ext, engineName);
}
engineNameMappings = null;
}
// 对于没有指定mapping的engine,取得其默认后缀
for (Map.Entry<String, TemplateEngine> entry : engines.entrySet()) {
String engineName = entry.getKey();
TemplateEngine engine = entry.getValue();
if (!remappedNames.contains(engineName)) {
String[] exts = engine.getDefaultExtensions();
for (String ext : exts) {
ext = normalizeExtension(ext);
assertNotNull(ext, "default extensions for engine: %s", engine);
engineMappings.put(ext, engine);
getLogger().debug("Template Name \"*.{}\" mapped to Template Engine: {}", ext, engineName);
}
}
}
// searching strategies
defaultExtension = normalizeExtension(defaultExtension);
List<TemplateSearchingStrategy> strategyList = createLinkedList();
if (defaultExtension != null) {
strategyList.add(new DefaultExtensionStrategy(defaultExtension));
}
if (searchExtensions) {
strategyList.add(new SearchExtensionsStrategy(getSupportedExtensions()));
}
if (searchLocalizedTemplates) {
strategyList.add(new SearchLocalizedTemplatesStrategy());
}
strategies = strategyList.toArray(new TemplateSearchingStrategy[strategyList.size()]);
}
/** 取得指定模板名后缀对应的engine。 */
public TemplateEngine getEngineOfName(String engineName) {
return engines.get(engineName);
}
/** 取得指定模板名后缀对应的engine。 */
public TemplateEngine getTemplateEngine(String extension) {
if (extension == null) {
return null; // prevent treemap from throwing npe
}
return engineMappings.get(extension);
}
/** 取得所有被登记的文件名后缀。 */
public String[] getSupportedExtensions() {
return engineMappings.keySet().toArray(new String[engineMappings.size()]);
}
/** 判断模板是否存在。 */
public boolean exists(String templateName) {
try {
findTemplate(templateName);
return true;
} catch (TemplateNotFoundException e) {
return false;
}
}
/** 渲染模板,并以字符串的形式取得渲染的结果。 */
public String getText(String templateName, TemplateContext context) throws TemplateException, IOException {
TemplateMatchResult result = findTemplate(templateName);
TemplateEngine engine = assertNotNull(result.getEngine(), "templateEngine");
return engine.getText(result.getTemplateName(), context);
}
/** 渲染模板,并将渲染的结果送到字节输出流中。 */
public void writeTo(String templateName, TemplateContext context, OutputStream ostream) throws TemplateException,
IOException {
TemplateMatchResult result = findTemplate(templateName);
TemplateEngine engine = assertNotNull(result.getEngine(), "templateEngine");
engine.writeTo(result.getTemplateName(), context, ostream);
}
/** 渲染模板,并将渲染的结果送到字符输出流中。 */
public void writeTo(String templateName, TemplateContext context, Writer writer) throws TemplateException,
IOException {
TemplateMatchResult result = findTemplate(templateName);
TemplateEngine engine = assertNotNull(result.getEngine(), "templateEngine");
engine.writeTo(result.getTemplateName(), context, writer);
}
/** 查找指定名称的模板。 */
TemplateMatchResult findTemplate(String templateName) {
assertInitialized();
TemplateKey key = new TemplateKey(templateName, strategies);
TemplateMatchResult result;
if (cacheEnabled) {
result = matchedTemplates.get(key);
if (result != null) {
return result;
}
}
TemplateMatcher matcher = new TemplateMatcher(key) {
private int i;
@Override
public boolean findTemplate() {
boolean found = false;
// 保存状态,假如没有匹配,则恢复状态
String savedTemplateNameWithoutExtension = getTemplateNameWithoutExtension();
String savedExtension = getExtension();
TemplateEngine savedEngine = getEngine();
int savedStrategyIndex = i;
try {
if (i < strategies.length) {
found = strategies[i++].findTemplate(this);
} else {
found = findTemplateInTemplateEngine(this);
}
} finally {
if (!found) {
// 恢复状态,以便尝试其它平级strategies
setTemplateNameWithoutExtension(savedTemplateNameWithoutExtension);
setExtension(savedExtension);
setEngine(savedEngine);
i = savedStrategyIndex;
}
}
return found;
}
};
if (!matcher.findTemplate()) {
throw new TemplateNotFoundException("Could not find template \"" + matcher.getOriginalTemplateName() + "\"");
}
if (cacheEnabled) {
result = new TemplateMatchResultImpl(matcher.getTemplateName(), matcher.getEngine());
matchedTemplates.put(key, result);
} else {
result = matcher;
}
return result;
}
/** 查找模板的最终strategy结点。 */
private boolean findTemplateInTemplateEngine(TemplateMatcher matcher) {
TemplateEngine engine = getTemplateEngine(matcher.getExtension());
matcher.setEngine(engine);
if (engine == null) {
return false;
}
String templateName = matcher.getTemplateName();
getLogger().trace("Searching for template \"{}\" using {}", templateName, engine);
return engine.exists(templateName);
}
}