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