package com.indeed.proctor.common; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.indeed.util.varexport.ManagedVariable; import com.indeed.util.varexport.VarExporter; import org.apache.log4j.Logger; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.el.FunctionMapper; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Objects; public class JsonProctorLoaderFactory { // Lenient parser used by consumer apps to prevent deployment order dependencies private static final Logger LOGGER = Logger.getLogger(JsonProctorLoaderFactory.class); private static final ObjectMapper OBJECT_MAPPER = Serializers.lenient(); // Prefix of export variable name for specifications. private static final String EXPORT_NAME_PREFIX_FOR_SPECIFICATION = "specification"; protected static final VarExporter VAR_EXPORTER = VarExporter.forNamespace(JsonProctorLoaderFactory.class.getSimpleName()); @Nullable protected String classResourcePath; @Nullable protected String filePath; @Nullable protected ProctorSpecification _specification; protected FunctionMapper functionMapper = RuleEvaluator.FUNCTION_MAPPER; protected List<ProctorLoadReporter> reporters = new ArrayList<>(); @SuppressWarnings("UnusedDeclaration") public void setClassResourcePath(@Nullable final String classResourcePath) { this.classResourcePath = classResourcePath; } public void setFilePath(@Nullable final String filePath) { this.filePath = filePath; } public void setSpecificationResource(@Nonnull final Resource specificationResource) { try (InputStream stream = specificationResource.getInputStream()) { this._specification = OBJECT_MAPPER.readValue(stream, ProctorSpecification.class); } catch (final IOException e) { throw new IllegalArgumentException("Unable to read proctor specification from " + specificationResource, e); } exportJsonSpecification( generateExportVariableName(specificationResource), this._specification ); } public void setSpecificationResource(@Nonnull final String specificationLocation) { if (specificationLocation.startsWith("classpath:")) { final String specificationClasspath = specificationLocation.replace("classpath:", ""); setSpecificationResource(new ClassPathResource(specificationClasspath, this.getClass())); } else { setSpecificationResource(new FileSystemResource(specificationLocation)); } } /** * setSpecificationResource() is likely more convenient to use instead of this method. */ public void setSpecification(@Nonnull final ProctorSpecification specification) { this._specification = Objects.requireNonNull( specification, "Null specifications are not supported" ); // Setting variable name with no suffix as no file name is given. exportJsonSpecification(EXPORT_NAME_PREFIX_FOR_SPECIFICATION, specification); } public void setFunctionMapper(@Nonnull final FunctionMapper functionMapper) { this.functionMapper = functionMapper; } @Nonnull public AbstractJsonProctorLoader getLoader() { if ((classResourcePath == null) == (filePath == null)) { throw new IllegalStateException("Must have exactly one of classResourcePath or filePath"); } final ProctorSpecification specification = Preconditions.checkNotNull( this._specification, "Missing specification" ); if (classResourcePath != null) { return new ClasspathProctorLoader(specification, classResourcePath, functionMapper); } final AbstractJsonProctorLoader loader = new FileProctorLoader(specification, filePath, functionMapper); loader.addLoadReporter(reporters); return loader; } /** * @param diffReporter a reporter to report changes of new proctor * @deprecated use {@link JsonProctorLoaderFactory#setLoadReporters} instead */ @Deprecated public void setDiffReporter(final AbstractProctorDiffReporter diffReporter) { Preconditions.checkNotNull(diffReporter, "diff reporter can't be null use AbstractProctorDiffReporter for nop implementation"); setLoadReporters(ImmutableList.of(diffReporter)); } public void setLoadReporters(final List<ProctorLoadReporter> reporters) { this.reporters = reporters; } private void exportJsonSpecification( final String variableName, final ProctorSpecification specification ) { try { final ManagedVariable<String> managedVariable = ManagedVariable.<String>builder() .setName(variableName) .setValue(OBJECT_MAPPER.writeValueAsString(specification)) .build(); VAR_EXPORTER.export(managedVariable); } catch (final JsonProcessingException e) { LOGGER.warn("Failed to expose json specification in VarExporter.", e); } } private String generateExportVariableName(final Resource resource) { return EXPORT_NAME_PREFIX_FOR_SPECIFICATION + "-" + resource.getFilename(); } }