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 }