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