/* * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.glowroot.ui; import java.util.List; import javax.annotation.Nullable; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Joiner; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Ordering; import io.netty.handler.codec.http.HttpResponseStatus; import org.immutables.value.Value; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.glowroot.common.live.LiveJvmService; import org.glowroot.common.live.LiveJvmService.AgentNotConnectedException; import org.glowroot.common.repo.ConfigRepository; import org.glowroot.common.repo.ConfigRepository.DuplicateMBeanObjectNameException; import org.glowroot.common.repo.util.Gauges; import org.glowroot.common.util.ObjectMappers; import org.glowroot.common.util.Styles; import org.glowroot.common.util.Versions; import org.glowroot.wire.api.model.AgentConfigOuterClass.AgentConfig; import org.glowroot.wire.api.model.AgentConfigOuterClass.AgentConfig.GaugeConfig; import org.glowroot.wire.api.model.AgentConfigOuterClass.AgentConfig.MBeanAttribute; import org.glowroot.wire.api.model.DownstreamServiceOuterClass.MBeanMeta; import static com.google.common.base.Preconditions.checkNotNull; import static io.netty.handler.codec.http.HttpResponseStatus.CONFLICT; @JsonService class GaugeConfigJsonService { private static final Logger logger = LoggerFactory.getLogger(GaugeConfigJsonService.class); private static final ObjectMapper mapper = ObjectMappers.create(); private static final Ordering<GaugeConfig> orderingByName = new Ordering<GaugeConfig>() { @Override public int compare(GaugeConfig left, GaugeConfig right) { Joiner joiner = Joiner.on('/'); return joiner.join(Gauges.displayPath(left.getMbeanObjectName())).compareToIgnoreCase( joiner.join(Gauges.displayPath(right.getMbeanObjectName()))); } }; private final ConfigRepository configRepository; private final @Nullable LiveJvmService liveJvmService; GaugeConfigJsonService(ConfigRepository configRepository, @Nullable LiveJvmService liveJvmService) { this.configRepository = configRepository; this.liveJvmService = liveJvmService; } @GET(path = "/backend/config/gauges", permission = "agent:config:view:gauge") String getGaugeConfig(@BindAgentId String agentId, @BindRequest GaugeConfigRequest request) throws Exception { Optional<String> version = request.version(); if (version.isPresent()) { GaugeConfig gaugeConfig = configRepository.getGaugeConfig(agentId, version.get()); if (gaugeConfig == null) { throw new JsonServiceException(HttpResponseStatus.NOT_FOUND); } return getGaugeResponse(agentId, gaugeConfig); } else { List<GaugeConfigWithWarningMessages> responses = Lists.newArrayList(); List<GaugeConfig> gaugeConfigs = configRepository.getGaugeConfigs(agentId); gaugeConfigs = orderingByName.immutableSortedCopy(gaugeConfigs); for (GaugeConfig gaugeConfig : gaugeConfigs) { responses.add(ImmutableGaugeConfigWithWarningMessages.builder() .config(GaugeConfigDto.create(gaugeConfig)) .build()); } return mapper.writeValueAsString(responses); } } @GET(path = "/backend/config/new-gauge-check-agent-connected", permission = "agent:config:edit:gauge") String checkAgentConnected(@BindAgentId String agentId) throws Exception { checkNotNull(liveJvmService); // agent:config:edit is disabled in offline viewer return Boolean.toString(liveJvmService.isAvailable(agentId)); } @GET(path = "/backend/config/matching-mbean-objects", permission = "agent:config:edit:gauge") String getMatchingMBeanObjects(@BindAgentId String agentId, @BindRequest MBeanObjectNameRequest request) throws Exception { checkNotNull(liveJvmService); // agent:config:edit is disabled in offline viewer try { return mapper.writeValueAsString(liveJvmService.getMatchingMBeanObjectNames(agentId, request.partialObjectName(), request.limit())); } catch (AgentNotConnectedException e) { logger.debug(e.getMessage(), e); return "[]"; } } @GET(path = "/backend/config/mbean-attributes", permission = "agent:config:edit:gauge") String getMBeanAttributes(@BindAgentId String agentId, @BindRequest MBeanAttributeNamesRequest request) throws Exception { checkNotNull(liveJvmService); // agent:config:edit is disabled in offline viewer boolean duplicateMBean = false; for (GaugeConfig gaugeConfig : configRepository.getGaugeConfigs(agentId)) { if (gaugeConfig.getMbeanObjectName().equals(request.objectName()) && !Versions.getVersion(gaugeConfig).equals(request.gaugeVersion())) { duplicateMBean = true; break; } } MBeanMeta mbeanMeta = liveJvmService.getMBeanMeta(agentId, request.objectName()); return mapper.writeValueAsString(ImmutableMBeanAttributeNamesResponse.builder() .duplicateMBean(duplicateMBean) .mbeanUnmatched(mbeanMeta.getUnmatched()) .mbeanUnavailable(mbeanMeta.getUnavailable()) .addAllMbeanAttributes(mbeanMeta.getAttributeNameList()) .build()); } @POST(path = "/backend/config/gauges/add", permission = "agent:config:edit:gauge") String addGauge(@BindAgentId String agentId, @BindRequest GaugeConfigDto gaugeConfigDto) throws Exception { GaugeConfig gaugeConfig = gaugeConfigDto.convert(); try { configRepository.insertGaugeConfig(agentId, gaugeConfig); } catch (DuplicateMBeanObjectNameException e) { // log exception at debug level logger.debug(e.getMessage(), e); throw new JsonServiceException(CONFLICT, "mbeanObjectName"); } return getGaugeResponse(agentId, gaugeConfig); } @POST(path = "/backend/config/gauges/update", permission = "agent:config:edit:gauge") String updateGauge(@BindAgentId String agentId, @BindRequest GaugeConfigDto gaugeConfigDto) throws Exception { GaugeConfig gaugeConfig = gaugeConfigDto.convert(); String version = gaugeConfigDto.version().get(); try { configRepository.updateGaugeConfig(agentId, gaugeConfig, version); } catch (DuplicateMBeanObjectNameException e) { // log exception at debug level logger.debug(e.getMessage(), e); throw new JsonServiceException(CONFLICT, "mbeanObjectName"); } return getGaugeResponse(agentId, gaugeConfig); } @POST(path = "/backend/config/gauges/remove", permission = "agent:config:edit:gauge") void removeGauge(@BindAgentId String agentId, @BindRequest GaugeConfigRequest request) throws Exception { configRepository.deleteGaugeConfig(agentId, request.version().get()); } private String getGaugeResponse(String agentId, GaugeConfig gaugeConfig) throws Exception { ImmutableGaugeResponse.Builder builder = ImmutableGaugeResponse.builder() .config(GaugeConfigDto.create(gaugeConfig)); MBeanMeta mbeanMeta = null; if (liveJvmService != null) { try { mbeanMeta = liveJvmService.getMBeanMeta(agentId, gaugeConfig.getMbeanObjectName()); } catch (AgentNotConnectedException e) { logger.debug(e.getMessage(), e); } } builder.agentNotConnected(mbeanMeta == null) .mbeanUnmatched(mbeanMeta != null && mbeanMeta.getUnmatched()) .mbeanUnavailable(mbeanMeta != null && mbeanMeta.getUnavailable()); if (mbeanMeta == null) { // agent not connected for (MBeanAttribute mbeanAttribute : gaugeConfig.getMbeanAttributeList()) { builder.addMbeanAvailableAttributeNames(mbeanAttribute.getName()); } } else { builder.addAllMbeanAvailableAttributeNames(mbeanMeta.getAttributeNameList()); } return mapper.writeValueAsString(builder.build()); } @Value.Immutable interface GaugeConfigWithWarningMessages { GaugeConfigDto config(); ImmutableList<String> warningMessages(); } @Value.Immutable interface GaugeConfigRequest { Optional<String> version(); } @Value.Immutable interface MBeanObjectNameRequest { String partialObjectName(); int limit(); } @Value.Immutable interface MBeanAttributeNamesRequest { String objectName(); @Nullable String gaugeVersion(); } @Value.Immutable interface MBeanAttributeNamesResponse { boolean mbeanUnavailable(); boolean mbeanUnmatched(); boolean duplicateMBean(); ImmutableList<String> mbeanAttributes(); } @Value.Immutable interface GaugeResponse { GaugeConfigDto config(); boolean agentNotConnected(); boolean mbeanUnavailable(); boolean mbeanUnmatched(); ImmutableList<String> mbeanAvailableAttributeNames(); } @Value.Immutable abstract static class GaugeConfigDto { abstract @Nullable String display(); // only used in response abstract List<String> displayPath(); // only used in response abstract String mbeanObjectName(); abstract ImmutableList<ImmutableMBeanAttributeDto> mbeanAttributes(); abstract Optional<String> version(); // absent for insert operations private GaugeConfig convert() { AgentConfig.GaugeConfig.Builder builder = GaugeConfig.newBuilder() .setMbeanObjectName(mbeanObjectName()); for (MBeanAttributeDto mbeanAttribute : mbeanAttributes()) { builder.addMbeanAttribute(mbeanAttribute.convert()); } return builder.build(); } private static GaugeConfigDto create(GaugeConfig gaugeConfig) { List<String> displayPath = Gauges.displayPath(gaugeConfig.getMbeanObjectName()); String display = Joiner.on(Gauges.DISPLAY_PATH_SEPARATOR).join(displayPath); ImmutableGaugeConfigDto.Builder builder = ImmutableGaugeConfigDto.builder() .display(display) .displayPath(displayPath) .mbeanObjectName(gaugeConfig.getMbeanObjectName()); for (MBeanAttribute mbeanAttribute : gaugeConfig.getMbeanAttributeList()) { builder.addMbeanAttributes(MBeanAttributeDto.create(mbeanAttribute)); } return builder.version(Versions.getVersion(gaugeConfig)) .build(); } } @Value.Immutable @Styles.AllParameters abstract static class MBeanAttributeDto { abstract String name(); abstract boolean counter(); private MBeanAttribute convert() { return MBeanAttribute.newBuilder() .setName(name()) .setCounter(counter()) .build(); } private static ImmutableMBeanAttributeDto create(MBeanAttribute mbeanAttribute) { return ImmutableMBeanAttributeDto.builder() .name(mbeanAttribute.getName()) .counter(mbeanAttribute.getCounter()) .build(); } } }