001 package org.kuali.common.util.metainf.service; 002 003 import java.io.File; 004 import java.io.IOException; 005 import java.util.ArrayList; 006 import java.util.Arrays; 007 import java.util.Collections; 008 import java.util.List; 009 import java.util.Properties; 010 011 import org.apache.commons.io.FileUtils; 012 import org.apache.commons.lang3.StringUtils; 013 import org.kuali.common.util.Assert; 014 import org.kuali.common.util.CollectionUtils; 015 import org.kuali.common.util.FileSystemUtils; 016 import org.kuali.common.util.LocationUtils; 017 import org.kuali.common.util.PropertyUtils; 018 import org.kuali.common.util.SimpleScanner; 019 import org.kuali.common.util.file.CanonicalFile; 020 import org.kuali.common.util.log.LoggerUtils; 021 import org.kuali.common.util.metainf.model.MetaInfContext; 022 import org.kuali.common.util.metainf.model.MetaInfResource; 023 import org.kuali.common.util.metainf.model.ScanResult; 024 import org.kuali.common.util.metainf.model.WriteLines; 025 import org.kuali.common.util.metainf.model.WriteProperties; 026 import org.kuali.common.util.metainf.model.WriteRequest; 027 import org.slf4j.Logger; 028 import org.slf4j.LoggerFactory; 029 030 public class DefaultMetaInfService implements MetaInfService { 031 032 private static final Logger logger = LoggerFactory.getLogger(DefaultMetaInfService.class); 033 034 protected static final String PROPERTIES = "properties"; 035 protected static final String SIZE = "size"; 036 protected static final String LINES = "lines"; 037 038 @Override 039 public ScanResult scan(MetaInfContext context) { 040 List<File> files = scanFileSystem(context); 041 List<MetaInfResource> resources = getResources(context, files); 042 return new ScanResult(context, resources); 043 } 044 045 @Override 046 public List<ScanResult> scan(List<MetaInfContext> contexts) { 047 List<ScanResult> results = new ArrayList<ScanResult>(); 048 for (MetaInfContext context : contexts) { 049 ScanResult result = scan(context); 050 results.add(result); 051 } 052 return results; 053 } 054 055 @Override 056 public void write(ScanResult result) { 057 write(Arrays.asList(result)); 058 } 059 060 protected WriteLines getWriteLines(ScanResult result) { 061 List<MetaInfResource> resources = result.getResources(); 062 List<String> locations = new ArrayList<String>(); 063 for (MetaInfResource resource : resources) { 064 locations.add(resource.getLocation()); 065 } 066 MetaInfContext context = result.getContext(); 067 File outputFile = context.getOutputFile(); 068 String encoding = context.getEncoding(); 069 File relativeDir = context.getRelativeDir(); 070 WriteRequest request = new WriteRequest(outputFile, encoding, relativeDir); 071 return new WriteLines(request, locations); 072 } 073 074 @Override 075 public void write(List<ScanResult> results) { 076 List<WriteLines> lines = getWriteLines(results); 077 List<WriteProperties> properties = getWriteProperties(results); 078 for (WriteLines element : CollectionUtils.toEmptyList(lines)) { 079 WriteRequest request = element.getRequest(); 080 if (!CollectionUtils.isEmpty(element.getLines())) { 081 String relativePath = FileSystemUtils.getRelativePathQuietly(request.getRelativeDir(), request.getOutputFile()); 082 logger.info("Creating [{}] - {} resources", relativePath, element.getLines().size()); 083 write(request, element.getLines()); 084 } 085 } 086 for (WriteProperties element : CollectionUtils.toEmptyList(properties)) { 087 WriteRequest request = element.getRequest(); 088 PropertyUtils.store(element.getProperties(), request.getOutputFile(), request.getEncoding()); 089 } 090 } 091 092 protected void write(WriteRequest request, List<String> lines) { 093 try { 094 FileUtils.writeLines(request.getOutputFile(), request.getEncoding(), lines); 095 } catch (IOException e) { 096 throw new IllegalArgumentException("Unexpected IO error", e); 097 } 098 } 099 100 protected List<WriteProperties> getWriteProperties(List<ScanResult> results) { 101 List<WriteProperties> requests = new ArrayList<WriteProperties>(); 102 for (ScanResult result : results) { 103 MetaInfContext context = result.getContext(); 104 if (context.isIncludePropertiesFile()) { 105 WriteProperties request = getWriteProperties(result); 106 requests.add(request); 107 } 108 } 109 return requests; 110 } 111 112 protected WriteProperties getWriteProperties(ScanResult result) { 113 List<MetaInfResource> resources = result.getResources(); 114 Properties properties = new Properties(); 115 for (MetaInfResource resource : resources) { 116 String key = getPropertyKey(resource.getLocation()); 117 String sizeKey = key + "." + SIZE; 118 String linesKey = key + "." + LINES; 119 properties.setProperty(sizeKey, Long.toString(resource.getSize())); 120 properties.setProperty(linesKey, Long.toString(resource.getLineCount())); 121 } 122 MetaInfContext context = result.getContext(); 123 File canonical = new CanonicalFile(context.getOutputFile()); 124 File outputFile = new File(canonical.getPath() + "." + PROPERTIES); 125 String encoding = context.getEncoding(); 126 File relativeDir = context.getRelativeDir(); 127 WriteRequest request = new WriteRequest(outputFile, encoding, relativeDir); 128 return new WriteProperties(request, properties); 129 } 130 131 protected List<WriteLines> getWriteLines(List<ScanResult> results) { 132 List<WriteLines> requests = new ArrayList<WriteLines>(); 133 for (ScanResult result : results) { 134 WriteLines request = getWriteLines(result); 135 requests.add(request); 136 } 137 return requests; 138 } 139 140 protected List<File> scanFileSystem(MetaInfContext context) { 141 File dir = context.getScanDir(); 142 Assert.isExistingDir(dir); 143 logger.debug("Examining [" + LocationUtils.getCanonicalPath(dir) + "]"); 144 List<String> includes = context.getIncludes(); 145 List<String> excludes = context.getExcludes(); 146 logger.debug("Patterns - {}", LoggerUtils.getLogMsg(includes, excludes)); 147 SimpleScanner scanner = new SimpleScanner(dir, includes, excludes); 148 return scanner.getFiles(); 149 } 150 151 protected List<MetaInfResource> getResources(MetaInfContext context, List<File> files) { 152 List<MetaInfResource> resources = new ArrayList<MetaInfResource>(); 153 for (File file : files) { 154 MetaInfResource resource = getResource(file, context); 155 resources.add(resource); 156 } 157 if (context.isSort()) { 158 Collections.sort(resources); 159 } 160 return resources; 161 } 162 163 protected MetaInfResource getResource(File resourceFile, MetaInfContext context) { 164 String location = getLocationURL(new CanonicalFile(resourceFile), context); 165 166 long lineCount = MetaInfResource.UNKNOWN_LINECOUNT; 167 168 // Only read through the file if we've been explicitly configured to do so 169 if (context.isIncludeLineCounts()) { 170 171 // Make sure an encoding has been supplied 172 Assert.noBlanks(context.getEncoding()); 173 174 // Read through the entire file keeping track of how many lines of text we encounter 175 lineCount = LocationUtils.getLineCount(resourceFile, context.getEncoding()); 176 } 177 178 // Create a resource object from the information we've collected 179 return new MetaInfResource(location, resourceFile.length(), lineCount); 180 } 181 182 protected String getLocationURL(CanonicalFile resourceFile, MetaInfContext context) { 183 if (!context.isRelativePaths()) { 184 return LocationUtils.getCanonicalURLString(resourceFile); 185 } else { 186 return getRelativeLocationURL(resourceFile, context); 187 } 188 } 189 190 /** 191 * Get a URL string that can be used to address <code>file</code>. This is usually a Spring pseudo-url classpath location, eg - [<code>classpath:foo/bar.txt</code>] 192 * 193 * @param resourceFile 194 * The file to get a location url for. eg - [<code>/x/y/z/src/main/resources/foo/bar.txt</code>] 195 * @param context 196 * Context information for generating a relative location url. eg - [<code>/x/y/z/src/main/resources</code>] and [<code>classpath:</code>]. 197 * 198 * @return A string representing a fully qualified location URL for <code>file</code>. eg - [<code>classpath:foo/bar.txt</code>] 199 */ 200 protected String getRelativeLocationURL(CanonicalFile resourceFile, MetaInfContext context) { 201 202 // Extract the parent directory 203 CanonicalFile parent = new CanonicalFile(context.getRelativeDir()); 204 205 // Make sure it is an existing directory 206 Assert.isExistingDir(parent); 207 208 // Get a string representing the path to the parent dir 209 String parentPath = parent.getPath(); 210 211 // Get a string representing the path to the resource file 212 String resourcePath = resourceFile.getPath(); 213 214 // Make sure the resource file resides underneath the parent dir 215 Assert.isTrue(StringUtils.contains(resourcePath, parentPath), "[" + resourcePath + "] does not contain [" + parentPath + "]"); 216 217 // Extract the portion of the path to the resource file that is relative to the parent dir 218 int relativePos = parentPath.length() + 1; 219 String relativePath = StringUtils.substring(resourcePath, relativePos); 220 221 // Prepend the prefix and return 222 return context.getUrlPrefix() + relativePath; 223 } 224 225 protected String getPropertyKey(String location) { 226 location = StringUtils.replace(location, ":", "."); 227 location = StringUtils.replace(location, "/", "."); 228 return location; 229 } 230 231 }