001package org.kuali.common.devops.metadata.logic;
002
003import static com.google.common.base.Optional.absent;
004import static com.google.common.base.Stopwatch.createStarted;
005import static com.google.common.collect.Lists.newArrayList;
006import static java.lang.String.format;
007import static org.apache.commons.lang3.StringUtils.isBlank;
008import static org.kuali.common.http.model.HttpStatus.SUCCESS;
009import static org.kuali.common.util.FormatUtils.getTime;
010import static org.kuali.common.util.base.Precondition.checkNotBlank;
011import static org.kuali.common.util.base.Precondition.checkNotNull;
012
013import java.util.List;
014import java.util.Properties;
015
016import org.kuali.common.devops.cache.Caches;
017import org.kuali.common.devops.metadata.function.FirstGCTimestampFunction;
018import org.kuali.common.devops.metadata.function.ManifestFunction;
019import org.kuali.common.devops.metadata.function.ProjectConfigUrlFragmentFunction;
020import org.kuali.common.devops.metadata.function.ProjectFunction;
021import org.kuali.common.devops.metadata.function.ProjectPropertiesUrlFragmentFunction;
022import org.kuali.common.devops.metadata.function.RemoteEnvironmentFunction;
023import org.kuali.common.devops.metadata.function.RicePropertiesFunction;
024import org.kuali.common.devops.metadata.function.TomcatVersionFunction;
025import org.kuali.common.devops.metadata.model.EnvironmentMetadata;
026import org.kuali.common.devops.metadata.model.MetadataUrl;
027import org.kuali.common.devops.metadata.model.RemoteEnvironment;
028import org.kuali.common.http.model.HttpContext;
029import org.kuali.common.http.model.HttpRequestResult;
030import org.kuali.common.http.model.HttpWaitResult;
031import org.kuali.common.util.log.LoggerUtils;
032import org.kuali.common.util.project.model.Project;
033import org.slf4j.Logger;
034
035import com.google.common.base.Function;
036import com.google.common.base.Optional;
037import com.google.common.base.Stopwatch;
038import com.google.common.cache.CacheBuilder;
039import com.google.common.cache.CacheLoader;
040import com.google.common.cache.LoadingCache;
041import com.google.common.collect.ImmutableList;
042
043public class DefaultEnvironmentMetadataService implements EnvironmentMetadataService {
044
045        private static final Logger logger = LoggerUtils.make();
046        private static final String PREFIX = "http://";
047        private static final String VERSION_SUFFIX = "/tomcat";
048        private static final String JSP_SUFFIX = "/tomcat/logs/env.jsp";
049        private static final String MANIFEST_SUFFIX = "/tomcat/webapps/ROOT/META-INF/MANIFEST.MF";
050        private static final String HEAP_LOG_SUFFIX = "/tomcat/logs/heap.log";
051        private final LoadingCache<String, HttpWaitResult> urlCache = getFastFileSystemCacher();
052
053        @Override
054        public EnvironmentMetadata getMetadata(String fqdn) {
055                return getMetadata(ImmutableList.of(fqdn)).get(0);
056        }
057
058        @Override
059        public List<EnvironmentMetadata> getMetadata(List<String> fqdns) {
060                List<EnvironmentMetadata> list = newArrayList();
061                for (String fqdn : fqdns) {
062                        logger.debug(format("examining -> [%s]", fqdn));
063                        EnvironmentMetadata meta = build(fqdn, urlCache);
064                        list.add(meta);
065                }
066                return list;
067        }
068
069        protected EnvironmentMetadata build(String fqdn, LoadingCache<String, HttpWaitResult> urlCache) {
070                MetadataUrlHelper helper = new MetadataUrlHelper(PREFIX, fqdn, urlCache);
071                HttpWaitResult fqdnStatus = urlCache.getUnchecked(PREFIX + fqdn);
072
073                EnvironmentMetadata.Builder builder = EnvironmentMetadata.builder();
074                builder.withFqdnStatus(fqdnStatus);
075                builder.tomcatVersion(build(helper, VERSION_SUFFIX, TomcatVersionFunction.create()));
076                builder.tomcatStartupTime(build(helper, HEAP_LOG_SUFFIX, new FirstGCTimestampFunction()));
077                builder.remoteEnvironment(build(helper, JSP_SUFFIX, new RemoteEnvironmentFunction()));
078                builder.manifest(build(helper, MANIFEST_SUFFIX, new ManifestFunction()));
079                addProject(helper, builder);
080                addConfig(helper, builder);
081                return builder.build();
082        }
083
084        protected void addProject(MetadataUrlHelper helper, EnvironmentMetadata.Builder builder) {
085                Optional<Properties> manifest = builder.getManifest().getMetadata();
086                if (manifest.isPresent()) {
087                        Function<Properties, Optional<String>> function = new ProjectPropertiesUrlFragmentFunction();
088                        Optional<String> suffix = function.apply(manifest.get());
089                        if (suffix.isPresent()) {
090                                builder.project(build(helper, suffix.get(), new ProjectFunction()));
091                        }
092                } else {
093                        builder.projectIsAbsent();
094                }
095        }
096
097        protected void addConfig(MetadataUrlHelper helper, EnvironmentMetadata.Builder builder) {
098                Optional<MetadataUrl<Project>> optionalProjectUrl = builder.getProject();
099                if (optionalProjectUrl == null || !optionalProjectUrl.isPresent()) {
100                        builder.configIsAbsent();
101                        return;
102                }
103                Optional<Project> optionalProject = optionalProjectUrl.get().getMetadata();
104                if (!optionalProject.isPresent()) {
105                        builder.configIsAbsent();
106                        return;
107                }
108                Optional<RemoteEnvironment> env = builder.getRemoteEnvironment().getMetadata();
109                if (optionalProject.isPresent()) {
110                        Function<Project, Optional<String>> function1 = new ProjectConfigUrlFragmentFunction(env);
111                        Function<Project, Optional<String>> function2 = new ProjectConfigUrlFragmentFunction(env, Optional.of("tomcat"));
112                        Optional<String> suffix1 = function1.apply(optionalProject.get());
113                        Optional<String> suffix2 = function2.apply(optionalProject.get());
114                        if (suffix1.isPresent() || suffix2.isPresent()) {
115                                builder.config(build(helper, suffix1, suffix2, new RicePropertiesFunction()));
116                        } else {
117                                builder.configIsAbsent();
118                        }
119                }
120        }
121
122        public static <T> MetadataUrl<T> build(MetadataUrlHelper helper, Function<String, T> converter) {
123                return build(helper, Optional.<String> absent(), Optional.<String> absent(), converter);
124        }
125
126        public static <T> MetadataUrl<T> build(MetadataUrlHelper helper, String suffix1, Function<String, T> converter) {
127                return build(helper, Optional.of(suffix1), Optional.<String> absent(), converter);
128        }
129
130        private static <T> MetadataUrl<T> build(MetadataUrlHelper helper, Optional<String> suffix1, Optional<String> suffix2, Function<String, T> converter) {
131                checkNotNull(helper, "helper");
132                checkNotBlank(suffix1, "suffix1");
133                checkNotBlank(suffix2, "suffix2");
134                checkNotNull(converter, "converter");
135                String url = helper.prefix + helper.fqdn + (suffix1.isPresent() ? suffix1.get() : "");
136                Stopwatch sw = createStarted();
137                HttpWaitResult result = helper.urlCache.getUnchecked(url);
138                logger.info(format("[%s] - %s", url, getTime(sw)));
139                Optional<String> content = getContent(result);
140                if (!content.isPresent() && suffix2.isPresent()) {
141                        sw = createStarted();
142                        url = helper.prefix + helper.fqdn + (suffix2.isPresent() ? suffix2.get() : "");
143                        logger.info(String.format("[%s] - %s", url, getTime(sw)));
144                        result = helper.urlCache.getUnchecked(url);
145                        content = getContent(result);
146                }
147                Optional<T> metadata = content.isPresent() ? Optional.of(converter.apply(content.get())) : Optional.<T> absent();
148                MetadataUrl.Builder<T> builder = MetadataUrl.builder();
149                return builder.url(url).content(content).converter(converter).metadata(metadata).build();
150        }
151
152        private static Optional<String> getContent(HttpWaitResult result) {
153                if (!SUCCESS.equals(result.getStatus())) {
154                        return absent();
155                }
156                HttpRequestResult request = result.getFinalRequestResult();
157                Optional<String> responseBody = request.getResponseBody();
158                if (!responseBody.isPresent() || isBlank(responseBody.get())) {
159                        return absent();
160                } else {
161                        return responseBody;
162                }
163        }
164
165        /**
166         * Grabs the first 25k in content from a URL and stashes it onto the local file system. Times out after 5 seconds, no re-tries.
167         */
168        protected LoadingCache<String, HttpWaitResult> getFastFileSystemCacher() {
169                HttpContext context = HttpContext.builder().quiet(true).maxBytes("25k").maxRetries(0).requestTimeout("5s").overallTimeout("5s").build();
170                CacheLoader<String, HttpWaitResult> loader = Caches.buildUrlCache(context);
171                return CacheBuilder.newBuilder().build(loader);
172        }
173
174        private static class MetadataUrlHelper {
175
176                private final String prefix;
177                private final String fqdn;
178                private final LoadingCache<String, HttpWaitResult> urlCache;
179
180                public MetadataUrlHelper(String prefix, String fqdn, LoadingCache<String, HttpWaitResult> urlCache) {
181                        this.prefix = checkNotBlank(prefix, "prefix");
182                        this.fqdn = checkNotBlank(fqdn, "fqdn");
183                        this.urlCache = checkNotNull(urlCache, "urlCache");
184                }
185        }
186}