/* * Copyright 2007-2009 the original author or authors. * * 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 net.paoding.rose.scanner; import static net.paoding.rose.RoseConstants.CONF_INTERCEPTED_ALLOW; import static net.paoding.rose.RoseConstants.CONF_INTERCEPTED_DENY; import static net.paoding.rose.RoseConstants.CONF_MODULE_IGNORED; import static net.paoding.rose.RoseConstants.CONF_MODULE_PATH; import static net.paoding.rose.RoseConstants.CONF_PARENT_MODULE_PATH; import static net.paoding.rose.RoseConstants.CONTROLLERS; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import net.paoding.rose.load.vfs.FileName; import net.paoding.rose.load.vfs.FileObject; import net.paoding.rose.load.vfs.FileSystemManager; import net.paoding.rose.load.LoadScope; import net.paoding.rose.load.ResourceRef; import net.paoding.rose.load.RoseScanner; import net.paoding.rose.util.RoseStringUtil; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.core.io.Resource; import org.springframework.util.Assert; import org.springframework.util.ResourceUtils; /** * * @author 王志亮 [qieqie.wang@gmail.com] * */ public class ModuleResourceProviderImpl implements ModuleResourceProvider { private Log logger = LogFactory.getLog(ModuleResourceProviderImpl.class); class Local { List<ModuleResource> moduleResourceList = new LinkedList<ModuleResource>(); Map<FileObject, ModuleResource> moduleResourceMap = new HashMap<FileObject, ModuleResource>(); } @Override public List<ModuleResource> findModuleResources(LoadScope scope) throws IOException { Local local = new Local(); String[] controllersScope = scope.getScope("controllers"); if (logger.isInfoEnabled()) { logger.info("[moduleResource] starting ..."); logger.info("[moduleResource] call 'findFiles':" + " to find classes or jar files by scope "// + Arrays.toString(controllersScope)); } List<ResourceRef> refers = RoseScanner.getInstance().getJarOrClassesFolderResources( controllersScope); if (logger.isInfoEnabled()) { logger.info("[moduleResource] exits from 'findFiles'"); logger.info("[moduleResource] going to scan controllers" + " from these folders or jar files:" + refers); } FileSystemManager fileSystem = new FileSystemManager(); for (ResourceRef refer : refers) { Resource resource = refer.getResource(); if (!refer.hasModifier("controllers")) { if (logger.isDebugEnabled()) { logger.debug("[moduleResource] Ignored because not marked as 'controllers'" + " in META-INF/rose.properties or META-INF/MANIFEST.MF: " + resource.getURI()); } continue; } File resourceFile = resource.getFile(); String urlString; if ("jar".equals(refer.getProtocol())) { urlString = ResourceUtils.URL_PROTOCOL_JAR + ":" + resourceFile.toURI() + ResourceUtils.JAR_URL_SEPARATOR; } else { urlString = resourceFile.toURI().toString(); } FileObject rootObject = fileSystem.resolveFile(urlString); if (rootObject == null || !rootObject.exists()) { if (logger.isDebugEnabled()) { logger.debug("[moduleResource] Ignored because not exists: " + urlString); } continue; } if (logger.isInfoEnabled()) { logger.info("[moduleResource] start to scan moduleResource in file: " + rootObject); } try { int oldSize = local.moduleResourceList.size(); deepScanImpl(local, rootObject, rootObject); int newSize = local.moduleResourceList.size(); if (logger.isInfoEnabled()) { logger.info("[moduleResource] got " + (newSize - oldSize) + " modules in " + rootObject); } } catch (Exception e) { logger.error("[moduleResource] error happend when scanning " + rootObject, e); } fileSystem.clearCache(); } afterScanning(local); logger.info("[moduleResource] found " + local.moduleResourceList.size() + " module resources "); return local.moduleResourceList; } protected void deepScanImpl(Local local, FileObject root, FileObject target) throws IOException { if (CONTROLLERS.equals(target.getName().getBaseName())) { checkModuleResourceCandidate(local, root, target, target); } else { FileObject[] children = target.getChildren(); for (FileObject child : children) { if (child.getType().hasChildren()) { deepScanImpl(local, root, child); } } } } protected void checkModuleResourceCandidate(Local local, FileObject root, FileObject topModuleFile, FileObject candidate) throws IOException { String relative = topModuleFile.getName().getRelativeName(candidate.getName()); String mappingPath = null; String[] interceptedAllow = null; String[] interceptedDeny = null; ModuleResource parentModule = local.moduleResourceMap.get(candidate.getParent()); // 如果rose.properties设置了controllers的module.path? FileObject rosePropertiesFile = candidate.getChild("rose.properties"); if (rosePropertiesFile != null && rosePropertiesFile.exists()) { Properties p = new Properties(); InputStream in = rosePropertiesFile.getContent().getInputStream(); p.load(in); in.close(); // 如果controllers=ignored,则... String ignored = p.getProperty(CONF_MODULE_IGNORED, "false").trim(); if ("true".equalsIgnoreCase(ignored) || "1".equalsIgnoreCase(ignored)) { if (logger.isInfoEnabled()) { logger.info("Ignored module(include submodules) by rose.properties[ignored=" + ignored + "]: " + candidate); } return; } mappingPath = p.getProperty(CONF_MODULE_PATH); if (mappingPath != null) { mappingPath = mappingPath.trim(); String parentModulePlaceHolder = "${" + CONF_PARENT_MODULE_PATH + "}"; if (mappingPath.indexOf(parentModulePlaceHolder) != -1) { String parentModulePath = ""; if (candidate.getParent() != null) { parentModulePath = (parentModule == null) ? "" : parentModule .getMappingPath(); } mappingPath = mappingPath.replace(parentModulePlaceHolder, parentModulePath); } if (mappingPath.length() != 0 && !mappingPath.startsWith("/")) { if (parentModule != null) { mappingPath = parentModule.getMappingPath() + "/" + mappingPath; } else if (StringUtils.isNotEmpty(relative)) { mappingPath = relative + "/" + mappingPath; } else { mappingPath = "/" + mappingPath; } } mappingPath = RoseStringUtil.mappingPath(mappingPath); } //interceptedAllow、interceptedDeny String interceptedAllowStrings = p.getProperty(CONF_INTERCEPTED_ALLOW); interceptedAllowStrings = StringUtils.trimToEmpty(interceptedAllowStrings); if (interceptedAllowStrings.length() > 0) { interceptedAllow = StringUtils.split(interceptedAllowStrings, ","); } String interceptedDenyStrings = p.getProperty(CONF_INTERCEPTED_DENY); interceptedDenyStrings = StringUtils.trimToEmpty(interceptedDenyStrings); if (interceptedDenyStrings.length() > 0) { interceptedDeny = StringUtils.split(interceptedDenyStrings, ","); } } // if (mappingPath == null) { if (parentModule != null) { mappingPath = parentModule.getMappingPath() + "/" + candidate.getName().getBaseName(); } else { mappingPath = ""; } } ModuleResource moduleResource = new ModuleResource(); moduleResource.setMappingPath(mappingPath); moduleResource.setModuleUrl(candidate.getURL()); moduleResource.setRelativePath(RoseStringUtil.relativePathToModulePath(relative)); moduleResource.setParent(parentModule); if (interceptedAllow != null) { moduleResource.setInterceptedAllow(interceptedAllow); } if (interceptedDeny != null) { moduleResource.setInterceptedDeny(interceptedDeny); } local.moduleResourceMap.put(candidate, moduleResource); local.moduleResourceList.add(moduleResource); if (logger.isDebugEnabled()) { logger.debug("found module '" + mappingPath + "' in " + candidate.getURL()); } FileObject[] children = candidate.getChildren(); for (FileObject child : children) { if (child.getType().hasContent() && !child.getType().hasChildren()) { handlerModuleResource(local, root, candidate, child); } } for (FileObject child : children) { if (child.getType().hasChildren()) { checkModuleResourceCandidate(local, root, topModuleFile, child); } } } protected void handlerModuleResource(Local local, FileObject rootObject, FileObject thisFolder, FileObject resource) throws IOException { FileName fileName = resource.getName(); String bn = fileName.getBaseName(); if (logger.isDebugEnabled()) { logger.debug("handlerModuleResource baseName=" + bn + "; file=" + fileName.getFileObject()); } if (bn.endsWith(".class") && bn.indexOf('$') == -1) { addModuleClass(local, rootObject, thisFolder, resource); } else if (bn.startsWith("applicationContext") && bn.endsWith(".xml")) { addModuleContext(local, rootObject, thisFolder, resource); } else if (bn.startsWith("messages") && (bn.endsWith(".xml") || bn.endsWith(".properties"))) { addModuleMessage(local, rootObject, thisFolder, resource); } } private void addModuleContext(Local local, FileObject rootObject, FileObject thisFolder, FileObject resource) throws IOException { ModuleResource moduleInfo = local.moduleResourceMap.get(thisFolder); moduleInfo.addContextResource(resource.getURL()); if (logger.isDebugEnabled()) { logger.debug("module '" + moduleInfo.getMappingPath() + "': found context file, url=" + resource.getURL()); } } private void addModuleMessage(Local local, FileObject rootObject, FileObject thisFolder, FileObject resource) throws IOException { ModuleResource moduleInfo = local.moduleResourceMap.get(thisFolder); String directory = resource.getParent().getURL().toString(); String messageFileName = resource.getName().getBaseName(); String msgBasename; if (messageFileName.indexOf('_') == -1) { msgBasename = messageFileName.substring(0, messageFileName.indexOf('.')); } else { msgBasename = messageFileName.substring(0, messageFileName.indexOf('_')); } moduleInfo.addMessageResource(directory + msgBasename); if (logger.isDebugEnabled()) { logger.debug("module '" + moduleInfo.getMappingPath() + "': found messages file, url=" + resource.getURL()); } } private void addModuleClass(Local local, FileObject rootObject, FileObject thisFolder, FileObject resource) throws IOException { String className = rootObject.getName().getRelativeName(resource.getName()); Assert.isTrue(!className.startsWith("/")); className = StringUtils.removeEnd(className, ".class"); className = className.replace('/', '.'); ModuleResource module = local.moduleResourceMap.get(thisFolder); try { // TODO: classloader... module.addModuleClass(Class.forName(className)); if (logger.isDebugEnabled()) { logger.debug("module '" + module.getMappingPath() + "': found class, name=" + className); } } catch (ClassNotFoundException e) { logger.error("", e); } } // FIXME: 如果一个module只有rose.properties文件也会从moduleInfoList中remove,以后是否需要修改? protected void afterScanning(Local local) { for (ModuleResource moduleResource : local.moduleResourceMap.values()) { if (moduleResource.getContextResources().size() == 0 && moduleResource.getModuleClasses().size() == 0) { local.moduleResourceList.remove(moduleResource); if (logger.isInfoEnabled()) { logger.info("remove empty module '" + moduleResource.getMappingPath() + "' " + moduleResource.getModuleUrl()); } } } } }