JavaValidationService.java

/*
 * Copyright (C) 2017 Red Hat, Inc.
 *
 * 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 io.atlasmap.java.module;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.atlasmap.core.validate.BaseModuleValidationService;
import io.atlasmap.java.v2.JavaField;
import io.atlasmap.spi.AtlasConversionService;
import io.atlasmap.spi.AtlasFieldActionService;
import io.atlasmap.spi.AtlasModuleDetail;
import io.atlasmap.spi.AtlasValidator;
import io.atlasmap.spi.FieldDirection;
import io.atlasmap.v2.Field;
import io.atlasmap.v2.Validation;
import io.atlasmap.v2.ValidationScope;
import io.atlasmap.v2.ValidationStatus;
import io.atlasmap.validators.NonNullValidator;

public class JavaValidationService extends BaseModuleValidationService<JavaField> {

    private static final Logger LOG = LoggerFactory.getLogger(JavaValidationService.class);
    private static Map<String, AtlasValidator> validatorMap = new HashMap<>();
    private AtlasModuleDetail moduleDetail = JavaModule.class.getAnnotation(AtlasModuleDetail.class);

    public JavaValidationService() {
        super();
        init();
    }

    public JavaValidationService(AtlasConversionService conversionService, AtlasFieldActionService fieldActionService) {
        super(conversionService, fieldActionService);
        init();
    }

    public void init() {
        setMappingFieldPairValidator(new JavaMappingFieldPairValidator(this, getConversionService()));
        NonNullValidator javaFilePathNonNullValidator = new NonNullValidator(ValidationScope.MAPPING,
                "Field path must not be null nor empty");
        NonNullValidator inputFieldTypeNonNullValidator = new NonNullValidator(ValidationScope.MAPPING,
                "FieldType should not be null nor empty");
        NonNullValidator outputFieldTypeNonNullValidator = new NonNullValidator(ValidationScope.MAPPING,
                "FieldType should not be null nor empty");
        NonNullValidator fieldTypeNonNullValidator = new NonNullValidator(ValidationScope.MAPPING,
                "Filed type should not be null nor empty");

        validatorMap.put("java.field.type.not.null", fieldTypeNonNullValidator);
        validatorMap.put("java.field.path.not.null", javaFilePathNonNullValidator);
        validatorMap.put("input.field.type.not.null", inputFieldTypeNonNullValidator);
        validatorMap.put("output.field.type.not.null", outputFieldTypeNonNullValidator);
    }

    public void destroy() {
        validatorMap.clear();
    }

    @Override
    protected AtlasModuleDetail getModuleDetail() {
        return moduleDetail;
    }

    @Override
    protected Class<JavaField> getFieldType() {
        return JavaField.class;
    }

    @Override
    protected String getModuleFieldName(JavaField field) {
        return field.getName() != null ? field.getName() : field.getPath();
    }

    protected void validateSourceAndTargetTypes(String mappingId, Field inputField, Field outField,
            List<Validation> validations) {
    }

    @Override
    protected void validateModuleField(String mappingId, JavaField field, FieldDirection direction,
            List<Validation> validations) {
        validatorMap.get("java.field.type.not.null").validate(field, validations, mappingId, ValidationStatus.WARN);
        if (direction == FieldDirection.SOURCE) {
            validatorMap.get("input.field.type.not.null").validate(field.getFieldType(), validations, mappingId,
                    ValidationStatus.WARN);
        } else {
            validatorMap.get("output.field.type.not.null").validate(field.getFieldType(), validations, mappingId,
                    ValidationStatus.WARN);
        }
        if (field.getPath() == null) {
            validatorMap.get("java.field.path.not.null").validate(field.getPath(), validations, mappingId);
        }

        validateClass(mappingId, field, validations);
    }

    private void validateClass(String mappingId, JavaField field, List<Validation> validations) {
        String clazzName = field.getClassName();
        if (clazzName != null && !clazzName.isEmpty()) {
            Integer major = detectClassVersion(clazzName);
            if (major != null) {
                if (major > new Double(System.getProperty("java.class.version")).intValue()) {
                    Validation validation = new Validation();
                    validation.setScope(ValidationScope.MAPPING);
                    validation.setId(mappingId);
                    validation.setMessage(String.format(
                            "Class '%s' for field is compiled against unsupported JDK version: %d current JDK: %s",
                            clazzName, major, System.getProperty("java.class.version")));
                    validation.setStatus(ValidationStatus.ERROR);
                    validations.add(validation);
                }
            } else {
                Validation validation = new Validation();
                validation.setScope(ValidationScope.MAPPING);
                validation.setId(mappingId);
                validation.setMessage(String.format("Class '%s' for field is not found on the classpath", clazzName));
                validation.setStatus(ValidationStatus.ERROR);
                validations.add(validation);
            }
        }
    }

    private Integer detectClassVersion(String className) {
        Integer major = null;
        InputStream in = null;
        try {
            in = getClass().getClassLoader().getResourceAsStream(className.replace('.', '/') + ".class");
            if (in == null) {
                in = ClassLoader.getSystemResourceAsStream(className.replace('.', '/') + ".class");
                if (in == null) {
                    return null;
                }
            }
            DataInputStream data = new DataInputStream(in);
            int magic = data.readInt();
            if (magic != 0xCAFEBABE) {
                LOG.error(String.format("Invalid Java class: %s magic value: %s", className, magic));
            }

            int minor = 0xFFFF & data.readShort();
            major = new Integer(0xFFFF & data.readShort());

            if (LOG.isDebugEnabled()) {
                LOG.debug(String.format("Detected class: %s version major: %s minor: %s", className, magic, minor));
            }
        } catch (IOException e) {
            LOG.error(String.format("Error detected version for class: %s msg: %s", className, e.getMessage()), e);
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException ie) {
                    LOG.error(String.format("Error closing input stream msg: %s", ie.getMessage()), ie);
                }
            }
        }
        return major;
    }

}