001/*
002 * Copyright 2012-2018 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.web.context;
018
019import java.io.File;
020import java.util.Locale;
021
022import org.apache.commons.logging.Log;
023import org.apache.commons.logging.LogFactory;
024
025import org.springframework.boot.system.SystemProperties;
026import org.springframework.context.ApplicationContext;
027import org.springframework.context.ApplicationListener;
028import org.springframework.util.Assert;
029import org.springframework.util.FileCopyUtils;
030import org.springframework.util.StringUtils;
031
032/**
033 * An {@link ApplicationListener} that saves embedded server port and management port into
034 * file. This application listener will be triggered whenever the server starts, and the
035 * file name can be overridden at runtime with a System property or environment variable
036 * named "PORTFILE" or "portfile".
037 *
038 * @author David Liu
039 * @author Phillip Webb
040 * @author Andy Wilkinson
041 * @since 2.0.0
042 */
043public class WebServerPortFileWriter
044                implements ApplicationListener<WebServerInitializedEvent> {
045
046        private static final String DEFAULT_FILE_NAME = "application.port";
047
048        private static final String[] PROPERTY_VARIABLES = { "PORTFILE", "portfile" };
049
050        private static final Log logger = LogFactory.getLog(WebServerPortFileWriter.class);
051
052        private final File file;
053
054        /**
055         * Create a new {@link WebServerPortFileWriter} instance using the filename
056         * 'application.port'.
057         */
058        public WebServerPortFileWriter() {
059                this(new File(DEFAULT_FILE_NAME));
060        }
061
062        /**
063         * Create a new {@link WebServerPortFileWriter} instance with a specified filename.
064         * @param filename the name of file containing port
065         */
066        public WebServerPortFileWriter(String filename) {
067                this(new File(filename));
068        }
069
070        /**
071         * Create a new {@link WebServerPortFileWriter} instance with a specified file.
072         * @param file the file containing port
073         */
074        public WebServerPortFileWriter(File file) {
075                Assert.notNull(file, "File must not be null");
076                String override = SystemProperties.get(PROPERTY_VARIABLES);
077                if (override != null) {
078                        this.file = new File(override);
079                }
080                else {
081                        this.file = file;
082                }
083        }
084
085        @Override
086        public void onApplicationEvent(WebServerInitializedEvent event) {
087                File portFile = getPortFile(event.getApplicationContext());
088                try {
089                        String port = String.valueOf(event.getWebServer().getPort());
090                        createParentFolder(portFile);
091                        FileCopyUtils.copy(port.getBytes(), portFile);
092                        portFile.deleteOnExit();
093                }
094                catch (Exception ex) {
095                        logger.warn(String.format("Cannot create port file %s", this.file));
096                }
097        }
098
099        /**
100         * Return the actual port file that should be written for the given application
101         * context. The default implementation builds a file from the source file and the
102         * application context namespace if available.
103         * @param applicationContext the source application context
104         * @return the file that should be written
105         */
106        protected File getPortFile(ApplicationContext applicationContext) {
107                String namespace = getServerNamespace(applicationContext);
108                if (StringUtils.isEmpty(namespace)) {
109                        return this.file;
110                }
111                String name = this.file.getName();
112                String extension = StringUtils.getFilenameExtension(this.file.getName());
113                name = name.substring(0, name.length() - extension.length() - 1);
114                if (isUpperCase(name)) {
115                        name = name + "-" + namespace.toUpperCase(Locale.ENGLISH);
116                }
117                else {
118                        name = name + "-" + namespace.toLowerCase(Locale.ENGLISH);
119                }
120                if (StringUtils.hasLength(extension)) {
121                        name = name + "." + extension;
122                }
123                return new File(this.file.getParentFile(), name);
124        }
125
126        private String getServerNamespace(ApplicationContext applicationContext) {
127                if (applicationContext instanceof WebServerApplicationContext) {
128                        return ((WebServerApplicationContext) applicationContext)
129                                        .getServerNamespace();
130                }
131                return null;
132        }
133
134        private boolean isUpperCase(String name) {
135                for (int i = 0; i < name.length(); i++) {
136                        if (Character.isLetter(name.charAt(i))
137                                        && !Character.isUpperCase(name.charAt(i))) {
138                                return false;
139                        }
140                }
141                return true;
142        }
143
144        private void createParentFolder(File file) {
145                File parent = file.getParentFile();
146                if (parent != null) {
147                        parent.mkdirs();
148                }
149        }
150
151}