/*
* 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.pull.impl;
import static com.alibaba.citrus.util.Assert.*;
import static com.alibaba.citrus.util.BasicConstant.*;
import static com.alibaba.citrus.util.CollectionUtil.*;
import static com.alibaba.citrus.util.StringUtil.*;
import static java.util.Collections.*;
import static org.springframework.web.context.request.RequestAttributes.*;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import com.alibaba.citrus.service.AbstractService;
import com.alibaba.citrus.service.pull.PullContext;
import com.alibaba.citrus.service.pull.PullException;
import com.alibaba.citrus.service.pull.PullService;
import com.alibaba.citrus.service.pull.RuntimeToolSetFactory;
import com.alibaba.citrus.service.pull.ToolFactory;
import com.alibaba.citrus.service.pull.ToolNameAware;
import com.alibaba.citrus.service.pull.ToolSetFactory;
import com.alibaba.citrus.util.ToStringBuilder;
import com.alibaba.citrus.util.ToStringBuilder.CollectionBuilder;
import com.alibaba.citrus.util.ToStringBuilder.MapBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
public class PullServiceImpl extends AbstractService<PullService> implements PullService, ApplicationContextAware {
private final static String DEFAULT_BEAN_NAME = "pullService";
private final static int SINGLETON = 0x1;
private final static int TOOL_FACTORY = 0x2;
private final static int TOOL_SET_FACTORY = 0x4;
private final static int RUNTIME_TOOL_SET_FACTORY = 0x8;
private final static AtomicInteger contextKeyCounter = new AtomicInteger();
private ApplicationContext beanFactory;
private PullService parent;
private String contextKey;
// toolName -> factory,包含所有类型的factories,初始化后即清除。
// 可能包含:
// 1. ToolFactory - non-singleton or singleton
// 2. ToolSetFactory - non-singleton or singleton
// 3. RuntimeToolSetFactory - non-singleton
private Map<String, Object> toolFactories;
// toolName -> toolFactory,non-singleton tool factories
private Map<String, ToolFactory> tools;
// toolName -> toolSetFactory,non-singleton tool set factories
private Map<String, ToolSetInfo<ToolSetFactory>> toolsInSet;
// toolName -> runtimeToolSetFactory
private Map<String, RuntimeToolSetFactory> toolsRuntime;
// 在初始化时,预先被pull的tools,有两种来源:
// 1. singleton toolFactory
// 2. singleton toolSetFactory
private Map<String, Object> prePulledTools;
// 所有非runtime tools的名称,包括:
// 1. singleton or non-singleton tools
// 1. singleton or non-singleton tools in set
private Set<ToolName> toolNames;
public void setApplicationContext(ApplicationContext factory) {
this.beanFactory = factory;
}
public void setParent(PullService parent) {
this.parent = parent;
}
public void setToolFactories(Map<String, Object> factories) {
this.toolFactories = factories;
}
@Override
protected void init() {
initParent();
initContextKey();
initToolFactories();
getLogger().info(
"Initialized pull service [key={}] "
+ "with {} pre-pulled tools, {} pre-queued tools and {} runtime tools", new Object[] { //
contextKey, prePulledTools.size(), tools.size() + toolsInSet.size(), toolsRuntime.size() });
}
/** 初始化parent pull service。 */
private void initParent() {
if (parent != null || beanFactory == null || beanFactory.getParent() == null) {
return;
}
// 取得parent pull service,依次尝试:
// 1. 在配置文件中明确设置parentRef
// 2. parent context中同名的对象
// 3. parent context中默认名称的对象
String parentBeanName = null;
if (beanFactory.getParent().containsBean(getBeanName())) {
parentBeanName = getBeanName();
} else if (beanFactory.getParent().containsBean(DEFAULT_BEAN_NAME)) {
parentBeanName = DEFAULT_BEAN_NAME;
}
if (parentBeanName != null) {
parent = (PullService) beanFactory.getParent().getBean(parentBeanName);
}
}
/** 创建一个在整个JVM中不重复的contextKey。 */
private void initContextKey() {
int i = contextKeyCounter.getAndIncrement();
contextKey = "PullService." + getBeanName() + (i > 0 ? "." + i : EMPTY_STRING);
}
/** 初始化所有tool factories。 */
private void initToolFactories() {
tools = createHashMap();
toolsInSet = createHashMap();
toolsRuntime = createHashMap();
prePulledTools = createHashMap();
toolNames = createHashSet();
if (toolFactories != null) {
for (Map.Entry<String, Object> e : toolFactories.entrySet()) {
String name = assertNotNull(trimToNull(e.getKey()), "tool name");
Object factory = e.getValue();
if (factory instanceof ToolNameAware) {
((ToolNameAware) factory).setToolName(name);
}
int type = getFactoryType(factory);
if (testBit(type, SINGLETON)) {
// 将singleton tool预先取值
if (testBit(type, TOOL_FACTORY)) {
Object tool;
try {
tool = encode(((ToolFactory) factory).createTool());
} catch (Exception ex) {
throw new PullException("Could not create tool: \"" + name + "\"", ex);
}
ToolName toolName = new ToolName(null, name, false);
toolNames.add(toolName);
prePulledTools.put(name, tool);
if (getLogger().isDebugEnabled()) {
getLogger().debug("Pre-pulled tool: {} = {}", toolName, tool);
}
}
// 将singleton tool set预先取得每一个值
if (testBit(type, TOOL_SET_FACTORY)) {
Iterable<String> names = ((ToolSetFactory) factory).getToolNames();
if (names != null) {
for (String nameInSet : names) {
nameInSet = trimToNull(nameInSet);
if (nameInSet != null) {
Object tool;
try {
tool = encode(((ToolSetFactory) factory).createTool(nameInSet));
} catch (Exception ex) {
throw new PullException("Could not create tool: \"" + name + "." + nameInSet
+ "\"", ex);
}
ToolName toolName = new ToolName(name, nameInSet, false);
toolNames.add(toolName);
prePulledTools.put(nameInSet, tool);
if (getLogger().isDebugEnabled()) {
getLogger().debug("Pre-pulled tool: {} = {}", toolName, tool);
}
}
}
}
}
} else {
// 将non-singleton tool,预先取得其名称。
if (testBit(type, TOOL_FACTORY)) {
ToolName toolName = new ToolName(null, name, false);
toolNames.add(toolName);
tools.put(name, (ToolFactory) factory);
getLogger().debug("Pre-queued tool: {}", toolName);
}
// 将non-singleton tool set,预先取得每一个名称。
if (testBit(type, TOOL_SET_FACTORY)) {
Iterable<String> names = ((ToolSetFactory) factory).getToolNames();
if (names != null) {
for (String nameInSet : names) {
nameInSet = trimToNull(nameInSet);
if (nameInSet != null) {
ToolName toolName = new ToolName(name, nameInSet, false);
toolNames.add(toolName);
toolsInSet.put(nameInSet, new ToolSetInfo<ToolSetFactory>(name,
(ToolSetFactory) factory, null));
getLogger().debug("Pre-queued tool: {}", toolName);
}
}
}
}
// 保存runtime tool
if (testBit(type, RUNTIME_TOOL_SET_FACTORY)) {
toolsRuntime.put(name, (RuntimeToolSetFactory) factory);
}
}
}
}
toolFactories = null;
}
public PullContext getContext() {
RequestAttributes attrs = null;
// 从request中取得context,假如当前不是在web环境中,则创建一个新的context,
// 从而确保在非web环境中也可以使用pull service。
try {
attrs = RequestContextHolder.currentRequestAttributes();
} catch (IllegalStateException e) {
getLogger().debug("Getting pull context in non-WEB environment: {}", e.getMessage());
}
PullContext context;
if (attrs == null) {
context = new PullContextImpl();
} else {
context = (PullContext) attrs.getAttribute(contextKey, SCOPE_REQUEST);
if (context == null) {
context = new PullContextImpl();
attrs.setAttribute(contextKey, context, SCOPE_REQUEST);
}
}
return context;
}
public Map<String, Object> getTools() {
return getContext().getTools();
}
private static int getFactoryType(Object factory) {
int type = 0;
if (factory instanceof RuntimeToolSetFactory) {
type |= RUNTIME_TOOL_SET_FACTORY;
if (factory instanceof ToolFactory) {
type |= TOOL_FACTORY;
}
return type;
}
if (factory instanceof ToolFactory) {
type |= TOOL_FACTORY;
if (((ToolFactory) factory).isSingleton()) {
type |= SINGLETON;
}
}
if (factory instanceof ToolSetFactory) {
type |= TOOL_SET_FACTORY;
if (((ToolSetFactory) factory).isSingleton()) {
type |= SINGLETON;
}
}
assertTrue(type != 0, "unknown pull tool factory type: %s", factory == null ? null : factory.getClass()
.getName());
return type;
}
private static boolean testBit(int type, int mask) {
return (type & mask) != 0;
}
private static Object encode(Object value) {
return value == null ? NULL_PLACEHOLDER : value;
}
private static Object decode(Object value) {
return value == NULL_PLACEHOLDER ? null : value;
}
@Override
public String toString() {
if (isInitialized()) {
ToStringBuilder sb = new ToStringBuilder().append("PullTools").append(
new CollectionBuilder().appendAll(toolNames).setPrintCount(true).setSort(true));
if (parent != null) {
sb.append("Parent ").append(parent);
}
return sb.toString();
} else {
return "PullTools[uninitialized]";
}
}
static class ToolSetInfo<F> {
private final String toolSetName;
private final F factory;
private final Object tool;
public ToolSetInfo(String toolSetName, F factory, Object tool) {
this.toolSetName = toolSetName;
this.factory = factory;
this.tool = tool;
}
public String getToolSetName() {
return toolSetName;
}
public F getFactory() {
return factory;
}
public Object getTool() {
return tool;
}
}
private class PullContextImpl implements PullContext {
private final PullContext parentContext;
private final Map<String, Object> pulledTools;
private final Map<String, RuntimeToolSetFactory> toolsRuntime;
private final Map<String, ToolSetInfo<RuntimeToolSetFactory>> toolsInRuntimeSet;
private final Set<ToolName> toolNames;
private Set<String> toolNamesIncludingParent;
private Map<String, Object> toolsIncludingParent;
private PullContextImpl() {
if (parent == null) {
parentContext = null;
} else {
parentContext = parent.getContext();
}
pulledTools = createHashMap();
toolsRuntime = createTreeMap(); // 排序以保证单元测试的确定性
toolsInRuntimeSet = createHashMap();
toolNames = createHashSet();
// copy runtime tools
toolsRuntime.putAll(PullServiceImpl.this.toolsRuntime);
// copy tool names and sort
toolNames.addAll(PullServiceImpl.this.toolNames);
}
public Object pull(String name) {
name = trimToNull(name);
if (name == null) {
return null;
}
Object tool;
// 如果name已经被pre-pulled,则直接返回
tool = prePulledTools.get(name);
if (tool == null) {
// 检查本地缓存,如果name早已存在,则直接返回
tool = pulledTools.get(name);
if (tool == null) {
tool = doPulling(name); // encoded tool
if (tool != null) {
pulledTools.put(name, tool);
}
}
}
// 如果有parent context,则试着从parent中取得
if (tool == null && parentContext != null) {
return parentContext.pull(name);
} else {
return decode(tool);
}
}
private Object doPulling(String name) {
// 如果存在于tools中,则pull之。
ToolFactory toolFactory = tools.get(name);
if (toolFactory != null) {
Object tool;
try {
tool = toolFactory.createTool();
} catch (Exception ex) {
throw new PullException("Could not create tool: \"" + name + "\"", ex);
}
if (getLogger().isDebugEnabled()) {
getLogger().debug("Pulled tool: {} = {}", name, tool);
}
return encode(tool);
}
// 如果存在于toolsInSet中,则pull之。
ToolSetInfo<ToolSetFactory> toolSetInfo = toolsInSet.get(name);
if (toolSetInfo != null) {
Object tool;
try {
tool = toolSetInfo.getFactory().createTool(name);
} catch (Exception ex) {
throw new PullException("Could not create tool: \"" + toolSetInfo.getToolSetName() + "." + name
+ "\"", ex);
}
if (getLogger().isDebugEnabled()) {
getLogger().debug("Pulled tool: {}.{} = {}",
new Object[] { toolSetInfo.getToolSetName(), name, tool });
}
return encode(tool);
}
// 如果存在于toolsInRuntimeSet中,则pull之。
pullToolsRuntime(name);
ToolSetInfo<RuntimeToolSetFactory> runtimeToolSetInfo = toolsInRuntimeSet.get(name);
if (runtimeToolSetInfo != null) {
Object tool;
try {
tool = runtimeToolSetInfo.getFactory().createTool(runtimeToolSetInfo.getTool(), name);
} catch (Exception ex) {
throw new PullException("Could not create tool: \"" + runtimeToolSetInfo.getToolSetName() + "."
+ name + "\"", ex);
}
if (getLogger().isDebugEnabled()) {
getLogger().debug("Pulled tool: {}.{} = {}",
new Object[] { runtimeToolSetInfo.getToolSetName(), name, tool });
}
return encode(tool);
}
return null;
}
private void pullToolsRuntime(String stopOnName) {
if (stopOnName != null && toolsInRuntimeSet.containsKey(stopOnName)) {
return;
}
for (Iterator<Map.Entry<String, RuntimeToolSetFactory>> i = toolsRuntime.entrySet().iterator(); i.hasNext(); ) {
Map.Entry<String, RuntimeToolSetFactory> entry = i.next();
i.remove();
String toolSetName = entry.getKey();
RuntimeToolSetFactory factory = entry.getValue();
int count = 0;
Object tool;
try {
tool = factory.createToolSet();
} catch (Exception ex) {
throw new PullException("Could not create runtime tool-set: \"" + toolSetName + "\"", ex);
}
Iterable<String> names = factory.getToolNames(tool);
if (names != null) {
for (String nameInSet : names) {
if (nameInSet != null) {
toolsInRuntimeSet.put(nameInSet, new ToolSetInfo<RuntimeToolSetFactory>(toolSetName,
factory, tool));
toolNames.add(new ToolName(toolSetName, nameInSet, false));
count++;
}
}
}
getLogger().debug("Queued {} tools for runtime tool-set \"{}\"", count, toolSetName);
// 假如已经找到了stopOnName,则立即返回
if (stopOnName != null && toolsInRuntimeSet.containsKey(stopOnName)) {
break;
}
}
}
public Set<String> getQualifiedToolNames() {
Set<String> names = createTreeSet();
for (ToolName toolName : populateToolNames()) {
names.add(toolName.getQualifiedName());
}
return names;
}
public Set<String> getToolNames() {
if (toolNamesIncludingParent == null) {
Set<String> names = createTreeSet();
for (ToolName toolName : populateToolNames()) {
names.add(toolName.getName());
}
toolNamesIncludingParent = unmodifiableSet(names);
}
return toolNamesIncludingParent;
}
private Set<ToolName> populateToolNames() {
Set<ToolName> toolNamesIncludingParent;
pullToolsRuntime(null);
if (parentContext == null) {
toolNamesIncludingParent = toolNames;
} else {
toolNamesIncludingParent = createHashSet();
for (String parentToolName : parentContext.getQualifiedToolNames()) {
toolNamesIncludingParent.add(new ToolName("_parent", parentToolName, true));
}
toolNamesIncludingParent.addAll(toolNames);
}
return toolNamesIncludingParent;
}
public Map<String, Object> getTools() {
if (toolsIncludingParent == null) {
for (String name : tools.keySet()) {
pull(name);
}
for (String name : toolsInSet.keySet()) {
pull(name);
}
pullToolsRuntime(null);
for (String name : toolsInRuntimeSet.keySet()) {
pull(name);
}
toolsIncludingParent = createHashMap();
if (parentContext != null) {
toolsIncludingParent.putAll(parentContext.getTools());
}
putAll(toolsIncludingParent, pulledTools);
putAll(toolsIncludingParent, prePulledTools);
toolsIncludingParent = unmodifiableMap(toolsIncludingParent);
}
return toolsIncludingParent;
}
private void putAll(Map<String, Object> tools, Map<String, Object> objects) {
for (Map.Entry<String, Object> entry : objects.entrySet()) {
tools.put(entry.getKey(), decode(entry.getValue()));
}
}
@Override
public String toString() {
MapBuilder mb = new MapBuilder();
mb.append("prePulledTools", new MapBuilder().appendAll(prePulledTools).setSortKeys(true)
.setPrintCount(true));
mb.append("pulledTools", new MapBuilder().appendAll(pulledTools).setSortKeys(true).setPrintCount(true));
ToStringBuilder sb = new ToStringBuilder().append("PullContext").append(mb);
if (parentContext != null) {
sb.append("Parent ").append(parentContext);
}
return sb.toString();
}
}
static final class ToolName implements Comparable<ToolName> {
private final String qname;
private final String name;
public ToolName(String namespace, String name, boolean parse) {
namespace = trimToNull(namespace);
name = assertNotNull(trimToNull(name), "tool name");
if (parse) {
name = trim(name, "/");
int index = name.lastIndexOf("/");
if (index >= 0) {
if (namespace == null) {
namespace = name.substring(0, index);
} else {
namespace = namespace + "/" + name.substring(0, index);
}
namespace = trim(namespace, "/");
name = name.substring(index + 1);
}
}
this.qname = namespace == null ? "/" + name : "/" + namespace + "/" + name;
this.name = name;
}
public String getQualifiedName() {
return qname;
}
public String getName() {
return name;
}
public int compareTo(ToolName o) {
return qname.compareTo(o.qname);
}
@Override
public int hashCode() {
return 31 + qname.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof ToolName)) {
return false;
}
return qname.equals(((ToolName) obj).qname);
}
@Override
public String toString() {
return qname;
}
}
}