Java tutorial
/* * Copyright 2013-2015 Hewlett-Packard Development Company, L.P. * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. */ package com.hp.autonomy.frontend.configuration.server; import com.autonomy.aci.client.services.AciService; import com.autonomy.aci.client.services.Processor; import com.autonomy.aci.client.transport.AciServerDetails; import com.autonomy.aci.client.util.AciParameters; import com.autonomy.nonaci.ServerDetails; import com.autonomy.nonaci.indexing.IndexingException; import com.autonomy.nonaci.indexing.IndexingService; import com.autonomy.nonaci.indexing.impl.IndexCommandImpl; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; import com.hp.autonomy.frontend.configuration.ConfigException; import com.hp.autonomy.frontend.configuration.SimpleComponent; import com.hp.autonomy.frontend.configuration.validation.OptionalConfigurationComponent; import com.hp.autonomy.frontend.configuration.validation.ValidationResult; import com.hp.autonomy.types.idol.marshalling.ProcessorFactory; import com.hp.autonomy.types.idol.marshalling.processors.NoopProcessor; import com.hp.autonomy.types.idol.responses.GetChildrenResponseData; import com.hp.autonomy.types.idol.responses.GetStatusResponseData; import com.hp.autonomy.types.idol.responses.GetVersionResponseData; import com.hp.autonomy.types.requests.idol.actions.general.GeneralActions; import com.hp.autonomy.types.requests.idol.actions.status.StatusActions; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.ToString; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.regex.Pattern; /** * Configuration for an ACI server, which can also include index and service ports. */ @SuppressWarnings({ "JavaDoc", "WeakerAccess", "DefaultAnnotationParam" }) @Getter @Builder(toBuilder = true) @EqualsAndHashCode(callSuper = false) @ToString @JsonDeserialize(builder = ServerConfig.ServerConfigBuilder.class) public class ServerConfig extends SimpleComponent<ServerConfig> implements OptionalConfigurationComponent<ServerConfig> { private static final Logger LOGGER = LoggerFactory.getLogger(ServerConfig.class); private static final int MAX_PORT = 65535; private final AciServerDetails.TransportProtocol protocol; private final String host; private final Integer port; private final ServerDetails.TransportProtocol indexProtocol; private final Integer indexPort; private final AciServerDetails.TransportProtocol serviceProtocol; private final Integer servicePort; /** * @return The producttypecsv of the server, used for validation */ private final Set<ProductType> productType; /** * @return The error message to expect when testing the index port of the server. If not defined it is assumed that * this server does not support indexing. */ private final String indexErrorMessage; /** * @return A Pattern used to match the product type. Useful for connectors. */ private final Pattern productTypeRegex; /** * Creates a new ServerConfig with the given ServerDetails for indexing * * @param serverDetails The ServerDetails to use * @return A new ServerConfig with the supplied indexing details */ public ServerConfig withIndexServer(final ServerDetails serverDetails) { return toBuilder().indexProtocol(serverDetails.getProtocol()).indexPort(serverDetails.getPort()).build(); } /** * Fetches the index and service ports from the component * * @param aciService The {@link AciService} used to discover the ports. * @param indexingService The {@link IndexingService} used to test the index port. This can be null if no index port is specified. * @param processorFactory Idol response parser generator * @return A new ServerConfig with its indexing and service details filled in. */ public ServerConfig fetchServerDetails(final AciService aciService, final IndexingService indexingService, final ProcessorFactory processorFactory) { final ServerConfigBuilder builder = toBuilder(); final Ports ports = determinePorts(aciService, processorFactory); if (ports.indexPort != null) { final ServerDetails indexDetails = new ServerDetails(); indexDetails.setHost(host); indexDetails.setPort(ports.indexPort); boolean isIndexPortValid = false; for (final ServerDetails.TransportProtocol protocol : Arrays .asList(ServerDetails.TransportProtocol.HTTP, ServerDetails.TransportProtocol.HTTPS)) { indexDetails.setProtocol(protocol); if (testIndexingConnection(indexDetails, indexingService, indexErrorMessage)) { // test http first. If the server is https, it will give an error (quickly), // whereas the timeout when doing https to a http server takes a really long time builder.indexProtocol(protocol); builder.indexPort(ports.indexPort); isIndexPortValid = true; break; } } if (!isIndexPortValid) { throw new IllegalArgumentException("Server does not have a valid index port"); } } final int servicePort = ports.servicePort; final AciServerDetails servicePortDetails = new AciServerDetails(); servicePortDetails.setHost(host); servicePortDetails.setPort(servicePort); for (final AciServerDetails.TransportProtocol protocol : Arrays .asList(AciServerDetails.TransportProtocol.HTTP, AciServerDetails.TransportProtocol.HTTPS)) { servicePortDetails.setProtocol(protocol); servicePortDetails.setPort(servicePort); if (testServicePortConnection(servicePortDetails, aciService)) { // test http first. If the server is https, it will give an error (quickly), // whereas the timeout when doing https to a http server takes a really long time builder.serviceProtocol(protocol); builder.servicePort(servicePort); //Both index and service ports are valid return builder.build(); } } //Index port valid but service port invalid throw new IllegalArgumentException("Server does not have a valid service port"); } private Ports determinePorts(final AciService aciService, final ProcessorFactory processorFactory) { try { // getStatus doesn't always return ports, but does when an index port is used final boolean useGetStatusToDeterminePorts = indexErrorMessage != null; if (useGetStatusToDeterminePorts) { final Processor<GetStatusResponseData> processor = processorFactory .getResponseDataProcessor(GetStatusResponseData.class); final GetStatusResponseData getStatusResponseData = aciService.executeAction(toAciServerDetails(), new AciParameters(StatusActions.GetStatus.name()), processor); return new Ports(getStatusResponseData.getAciport(), getStatusResponseData.getIndexport(), getStatusResponseData.getServiceport()); } else { final Processor<GetChildrenResponseData> processor = processorFactory .getResponseDataProcessor(GetChildrenResponseData.class); final GetChildrenResponseData responseData = aciService.executeAction(toAciServerDetails(), new AciParameters(GeneralActions.GetChildren.name()), processor); return new Ports(responseData.getPort(), null, responseData.getServiceport()); } } catch (final RuntimeException e) { throw new IllegalArgumentException("Unable to connect to ACI server", e); } } private boolean testServicePortConnection(final AciServerDetails serviceDetails, final AciService aciService) { try { aciService.executeAction(serviceDetails, new AciParameters("getstatus"), new NoopProcessor()); return true; } catch (final RuntimeException ignored) { return false; } } private boolean testIndexingConnection(final ServerDetails indexDetails, final IndexingService indexingService, final CharSequence errorMessage) { try { indexingService.executeCommand(indexDetails, new IndexCommandImpl("test")); } catch (final IndexingException e) { // we got back a response from the index port return e.getMessage().contains(errorMessage); } catch (final RuntimeException ignored) { // any other kind of exception is bad } return false; } /** * @return A representation of this server as an {@link AciServerDetails} */ public AciServerDetails toAciServerDetails() { return new AciServerDetails(protocol, host, port); } /** * @return A representation of this server as an {@link ServerDetails} */ public ServerDetails toServerDetails() { final ServerDetails serverDetails = new ServerDetails(); serverDetails.setHost(host); serverDetails.setPort(indexPort); serverDetails.setProtocol(indexProtocol); return serverDetails; } /** * Validates that the required settings are supplied and that the target server is responding * * @param aciService The {@link AciService} to use for validation * @param indexingService The {@link IndexingService} to use for validation. If the server does not support indexing * this may be null * @param processorFactory The {@link ProcessorFactory} * @return A {@link ValidationResult} which will be * <ul> * <li>Valid if the server config is valid</li> * <li>If it is not valid because the given server is not of the require type, the data will be a {@link IncorrectServerType}, * containing a list of valid server types</li> * <li>If it is invalid for any other reason, the data will be a {@link ServerConfig.Validation}</li> * </ul> */ public ValidationResult<?> validate(final AciService aciService, final IndexingService indexingService, final ProcessorFactory processorFactory) { // if the host is blank further testing is futile try { // string doesn't matter here as we swallow the exception basicValidate(null); } catch (final ConfigException ignored) { return new ValidationResult<>(false, Validation.REQUIRED_FIELD_MISSING); } final boolean isCorrectVersion; try { isCorrectVersion = testServerVersion(aciService, processorFactory); } catch (final RuntimeException e) { LOGGER.debug("Error validating server version for {}", productType); LOGGER.debug("", e); return new ValidationResult<>(false, Validation.CONNECTION_ERROR); } if (!isCorrectVersion) { if (productTypeRegex == null) { final List<String> friendlyNames = new ArrayList<>(); for (final ProductType productType : this.productType) { friendlyNames.add(productType.getFriendlyName()); } return new ValidationResult<>(false, new IncorrectServerType(friendlyNames)); } else { // can't use friendly names for regex return new ValidationResult<Object>(false, Validation.REGULAR_EXPRESSION_MATCH_ERROR); } } try { final ServerConfig serverConfig = fetchServerDetails(aciService, indexingService, processorFactory); final boolean result = serverConfig.servicePort > 0; final boolean indexPortPresent = indexErrorMessage != null; return indexPortPresent ? new ValidationResult<>(result && serverConfig.indexPort > 0, Validation.SERVICE_OR_INDEX_PORT_ERROR) : new ValidationResult<>(result, Validation.SERVICE_PORT_ERROR); } catch (final RuntimeException e) { LOGGER.debug("Error validating config", e); return new ValidationResult<>(false, Validation.FETCH_PORT_ERROR); } } /** * @param component The name of the configuration section, to be used in case of failure * @throws ConfigException If the ServerConfig is invalid */ @Override public void basicValidate(final String component) throws ConfigException { if (port == null || port <= 0 || port > MAX_PORT) { throw new ConfigException(component, component + ": port number must be between 1 and 65535."); } else if (StringUtils.isBlank(host)) { throw new ConfigException(component, component + ": host name must not be blank."); } } private boolean testServerVersion(final AciService aciService, final ProcessorFactory processorFactory) { // Community's ProductName is just IDOL, so we need to check the product type final GetVersionResponseData versionResponseData = aciService.executeAction(toAciServerDetails(), new AciParameters(GeneralActions.GetVersion.name()), processorFactory.getResponseDataProcessor(GetVersionResponseData.class)); final Collection<String> serverProductTypes = new HashSet<>( Arrays.asList(versionResponseData.getProducttypecsv().split(","))); return productTypeRegex == null ? productType.stream().anyMatch(p -> serverProductTypes.contains(p.name())) : serverProductTypes.stream() .anyMatch(serverProductType -> productTypeRegex.matcher(serverProductType).matches()); } /** * @return The service port details of this ServerConfig as an {@link AciServerDetails} */ public AciServerDetails toServiceServerDetails() { return new AciServerDetails(serviceProtocol, host, servicePort); } @Override @JsonIgnore public Boolean getEnabled() { return true; } public enum Validation { REQUIRED_FIELD_MISSING, CONNECTION_ERROR, SERVICE_PORT_ERROR, SERVICE_OR_INDEX_PORT_ERROR, FETCH_PORT_ERROR, INCORRECT_SERVER_TYPE, REGULAR_EXPRESSION_MATCH_ERROR } @Data @AllArgsConstructor(access = AccessLevel.PACKAGE) public static class IncorrectServerType { private final Validation validation = Validation.INCORRECT_SERVER_TYPE; private final List<String> friendlyNames; } @SuppressWarnings({ "FieldMayBeFinal", "unused" }) @JsonPOJOBuilder(withPrefix = "") @JsonIgnoreProperties(ignoreUnknown = true) // for compatibility with old AciServerDetails config files public static class ServerConfigBuilder { private AciServerDetails.TransportProtocol protocol = AciServerDetails.TransportProtocol.HTTP; private AciServerDetails.TransportProtocol serviceProtocol = AciServerDetails.TransportProtocol.HTTP; private ServerDetails.TransportProtocol indexProtocol = ServerDetails.TransportProtocol.HTTP; private Pattern productTypeRegex; @JsonProperty("productTypeRegex") public String getProductTypeRegexAsString() { return Objects.toString(productTypeRegex); } @JsonProperty("productTypeRegex") public ServerConfigBuilder productTypeRegexFromString(final String productTypeRegex) { this.productTypeRegex = Pattern.compile(productTypeRegex); return this; } } @AllArgsConstructor private static class Ports { final int aciPort; final Integer indexPort; final int servicePort; } }