/*
* 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.resource.support;
import static com.alibaba.citrus.service.resource.ResourceLoadingService.*;
import static com.alibaba.citrus.util.Assert.*;
import static com.alibaba.citrus.util.CollectionUtil.*;
import static com.alibaba.citrus.util.ObjectUtil.*;
import static com.alibaba.citrus.util.StringUtil.*;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Set;
import com.alibaba.citrus.service.resource.ResourceLoadingService;
import com.alibaba.citrus.service.resource.ResourceNotFoundException;
import com.alibaba.citrus.springext.ResourceLoadingExtender;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.io.AbstractResource;
import org.springframework.core.io.ContextResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.StringUtils;
/**
* 将<code>ResourceLoadingService</code>整合到Spring <code>ApplicationContext</code>
* 中。
*
* @author Michael Zhou
*/
public class ResourceLoadingSupport implements ResourceLoadingExtender, ApplicationListener {
private final static Logger log = LoggerFactory.getLogger(ResourceLoadingSupport.class);
private final ApplicationContext factory;
private final String resourceLoadingServiceName;
private final ResourcePatternResolver resolver;
private ResourceLoadingService resourceLoadingService;
private boolean contextRefreshed = false;
private boolean complained = false;
/**
* 创建<code>ResourceLoadingSupport</code>,并指定
* <code>ResourceLoadingService</code>所在的bean factory。
*/
public ResourceLoadingSupport(ApplicationContext factory) {
this(factory, null);
}
/**
* 创建<code>ResourceLoadingSupport</code>,并指定
* <code>ResourceLoadingService</code>所在的bean factory,以及
* <code>ResourceLoadingService</code>的名称。
*/
public ResourceLoadingSupport(ApplicationContext factory, String resourceLoadingServiceName) {
this.factory = assertNotNull(factory, "beanFactory");
this.resourceLoadingServiceName = defaultIfNull(trimToNull(resourceLoadingServiceName),
"resourceLoadingService");
this.resolver = new ResourceLoadingServicePatternResolver();
}
/** 当applicatioon context被refresh后,调用此方法初始化。 */
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextRefreshedEvent) {
contextRefreshed = true;
resourceLoadingService = getResourceLoadingServiceFromContext();
}
}
/** 取得<code>ResourceLoadingService</code>,如果还未初始化或不存在,则返回<code>null</code>。 */
public ResourceLoadingService getResourceLoadingService() {
if (contextRefreshed) {
return resourceLoadingService;
}
return getResourceLoadingServiceFromContext();
}
private ResourceLoadingService getResourceLoadingServiceFromContext() {
try {
return (ResourceLoadingService) factory.getBean(resourceLoadingServiceName);
} catch (IllegalStateException e) {
// beanFactory未准备好,试一下parent factory。如果均取不到ResourceLoadingService,返回null,不打日志
ApplicationContext parent = factory.getParent();
if (parent != null) {
try {
return (ResourceLoadingService) parent.getBean(resourceLoadingServiceName);
} catch (Exception ee) {
}
}
} catch (NoSuchBeanDefinitionException e) {
if (!complained) {
complained = true;
log.warn("ResourceLoadingService does not exists: beanName={}", resourceLoadingServiceName);
}
}
return null;
}
/**
* 取得指定路径名称所代表的资源对象。
* <p>
* 如果返回<code>null</code>表示使用原来的装载机制来取得资源。
* </p>
*/
public Resource getResourceByPath(String path) {
ResourceLoadingService resourceLoadingService = getResourceLoadingService();
if (resourceLoadingService == null) {
// 如果resource loading service不存在,则返回null,调用原来的装载机制来取得资源。
return null;
}
com.alibaba.citrus.service.resource.Resource resource;
try {
resource = resourceLoadingService.getResource(path, FOR_CREATE);
} catch (IllegalStateException e) {
// resourceLoadingService未准备好,有可能是在初始化resource loading service的过程中,
// 某个loader或filter通过spring resource loader注入resource,从而产生递归调用。
// 此时返回null,调用原来的装载机制来取得资源。
return null;
} catch (ResourceNotFoundException e) {
return new NonExistResource(path, e);
}
return new ResourceAdapter(path, resource, this);
}
/** 取得用来解析resource pattern的解析器。 */
public ResourcePatternResolver getResourcePatternResolver() {
return resolver;
}
/** 用<code>ResourceLoadingService</code>来解析resource pattern。 */
private class ResourceLoadingServicePatternResolver extends PathMatchingResourcePatternResolver {
public ResourceLoadingServicePatternResolver() {
super(factory);
}
@Override
@SuppressWarnings("unchecked")
protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern)
throws IOException {
ResourceLoadingService resourceLoadingService = getResourceLoadingService();
// 如果resource loading service不存在,或者resource不是从resource loading service取得的,
// 则调用原来的装载机制来取得资源。
if (resourceLoadingService == null || !(rootDirResource instanceof ResourceAdapter)) {
return super.doFindPathMatchingFileResources(rootDirResource, subPattern);
}
ResourceAdapter rootResource = (ResourceAdapter) rootDirResource;
String path = rootResource.getPathWithinContext();
if (!path.endsWith("/")) {
path += "/";
}
String fullPattern = path + subPattern;
Set<Resource> result = createLinkedHashSet();
findMatchingResources(resourceLoadingService, fullPattern, path, result);
return result;
}
private void findMatchingResources(ResourceLoadingService resourceLoadingService, String fullPattern,
String dir, Set<Resource> result) throws IOException {
String[] candidates;
try {
candidates = resourceLoadingService.list(dir);
} catch (ResourceNotFoundException e) {
return;
}
boolean dirDepthNotFixed = fullPattern.indexOf("**") != -1;
for (String name : candidates) {
String currPath = dir + name;
if (currPath.endsWith("/")
&& (dirDepthNotFixed || StringUtils.countOccurrencesOf(currPath, "/") <= StringUtils
.countOccurrencesOf(fullPattern, "/"))) {
findMatchingResources(resourceLoadingService, fullPattern, currPath, result);
}
if (getPathMatcher().match(fullPattern, currPath)) {
try {
result.add(new ResourceAdapter(currPath, resourceLoadingService.getResource(currPath)));
} catch (ResourceNotFoundException e) {
// ignore
}
}
}
}
}
/** 一个特殊的resource:代表资源未找到。 */
private static class NonExistResource extends AbstractResource implements ContextResource {
private final String location;
private final IOException ioe;
private final String description;
public NonExistResource(String location, ResourceNotFoundException e) {
this.location = location;
this.ioe = new IOException("Resource Not Found [" + location + "]");
this.ioe.initCause(e);
this.description = "NonExistResource[" + location + "]";
}
public String getPathWithinContext() {
return location;
}
public String getDescription() {
return description;
}
@Override
public URL getURL() throws IOException {
throw (IOException) ioe.fillInStackTrace();
}
@Override
public File getFile() throws IOException {
throw (IOException) ioe.fillInStackTrace();
}
public InputStream getInputStream() throws IOException {
throw (IOException) ioe.fillInStackTrace();
}
@Override
public Resource createRelative(String relativePath) throws IOException {
throw (IOException) ioe.fillInStackTrace();
}
}
}