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