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}