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    }