001/*
002 * Copyright 2012-2016 the original author or authors.
003 *
004 * Licensed under the Apache 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.apache.org/licenses/LICENSE-2.0
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 */
016
017package org.springframework.boot.loader.tools;
018
019import java.io.File;
020import java.io.FileOutputStream;
021import java.io.IOException;
022import java.text.SimpleDateFormat;
023import java.util.Date;
024import java.util.Map;
025import java.util.Map.Entry;
026import java.util.Properties;
027
028/**
029 * A {@code BuildPropertiesWriter} writes the {@code build-info.properties} for
030 * consumption by the Actuator.
031 *
032 * @author Andy Wilkinson
033 * @author Stephane Nicoll
034 */
035public final class BuildPropertiesWriter {
036
037        private final File outputFile;
038
039        /**
040         * Creates a new {@code BuildPropertiesWriter} that will write to the given
041         * {@code outputFile}.
042         * @param outputFile the output file
043         */
044        public BuildPropertiesWriter(File outputFile) {
045                this.outputFile = outputFile;
046        }
047
048        public void writeBuildProperties(ProjectDetails projectDetails) throws IOException {
049                Properties properties = createBuildInfo(projectDetails);
050                createFileIfNecessary(this.outputFile);
051                FileOutputStream outputStream = new FileOutputStream(this.outputFile);
052                try {
053                        properties.store(outputStream, "Properties");
054                }
055                finally {
056                        try {
057                                outputStream.close();
058                        }
059                        catch (IOException ex) {
060                                // Continue
061                        }
062                }
063        }
064
065        private void createFileIfNecessary(File file) throws IOException {
066                if (file.exists()) {
067                        return;
068                }
069                File parent = file.getParentFile();
070                if (!parent.isDirectory() && !parent.mkdirs()) {
071                        throw new IllegalStateException("Cannot create parent directory for '"
072                                        + this.outputFile.getAbsolutePath() + "'");
073                }
074                if (!file.createNewFile()) {
075                        throw new IllegalStateException("Cannot create target file '"
076                                        + this.outputFile.getAbsolutePath() + "'");
077                }
078        }
079
080        protected Properties createBuildInfo(ProjectDetails project) {
081                Properties properties = new Properties();
082                properties.put("build.group", project.getGroup());
083                properties.put("build.artifact", project.getArtifact());
084                properties.put("build.name", project.getName());
085                properties.put("build.version", project.getVersion());
086                properties.put("build.time", formatDate(new Date()));
087                if (project.getAdditionalProperties() != null) {
088                        for (Map.Entry<String, String> entry : project.getAdditionalProperties()
089                                        .entrySet()) {
090                                properties.put("build." + entry.getKey(), entry.getValue());
091                        }
092                }
093                return properties;
094        }
095
096        private String formatDate(Date date) {
097                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
098                return sdf.format(date);
099        }
100
101        /**
102         * Build-system agnostic details of a project.
103         */
104        public static final class ProjectDetails {
105
106                private final String group;
107
108                private final String artifact;
109
110                private final String name;
111
112                private final String version;
113
114                private final Map<String, String> additionalProperties;
115
116                public ProjectDetails(String group, String artifact, String version, String name,
117                                Map<String, String> additionalProperties) {
118                        this.group = group;
119                        this.artifact = artifact;
120                        this.name = name;
121                        this.version = version;
122                        validateAdditionalProperties(additionalProperties);
123                        this.additionalProperties = additionalProperties;
124                }
125
126                private static void validateAdditionalProperties(
127                                Map<String, String> additionalProperties) {
128                        if (additionalProperties != null) {
129                                for (Entry<String, String> property : additionalProperties.entrySet()) {
130                                        if (property.getValue() == null) {
131                                                throw new NullAdditionalPropertyValueException(property.getKey());
132                                        }
133                                }
134                        }
135                }
136
137                public String getGroup() {
138                        return this.group;
139                }
140
141                public String getArtifact() {
142                        return this.artifact;
143                }
144
145                public String getName() {
146                        return this.name;
147                }
148
149                public String getVersion() {
150                        return this.version;
151                }
152
153                public Map<String, String> getAdditionalProperties() {
154                        return this.additionalProperties;
155                }
156
157        }
158
159        /**
160         * Exception thrown when an additional property with a null value is encountered.
161         */
162        public static class NullAdditionalPropertyValueException
163                        extends IllegalArgumentException {
164
165                public NullAdditionalPropertyValueException(String name) {
166                        super("Additional property '" + name + "' is illegal as its value is null");
167                }
168
169        }
170
171}