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}