/** * Copyright 2015 StreamSets Inc. * * Licensed under the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 com.streamsets.datacollector.definition; import com.google.common.collect.ImmutableSet; import com.streamsets.datacollector.el.ElConstantDefinition; import com.streamsets.datacollector.el.ElFunctionArgumentDefinition; import com.streamsets.datacollector.el.ElFunctionDefinition; import com.streamsets.datacollector.el.JvmEL; import com.streamsets.datacollector.el.RuntimeEL; import com.streamsets.pipeline.api.ElConstant; import com.streamsets.pipeline.api.ElFunction; import com.streamsets.pipeline.api.ElParam; import com.streamsets.pipeline.api.impl.ErrorMessage; import com.streamsets.pipeline.api.impl.Utils; import com.streamsets.pipeline.lib.el.Base64EL; import com.streamsets.pipeline.lib.el.FileEL; import com.streamsets.pipeline.lib.el.MathEL; import com.streamsets.datacollector.el.PipelineEL; import com.streamsets.pipeline.lib.el.StringEL; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Pattern; public abstract class ELDefinitionExtractor { static final Class[] DEFAULT_EL_DEFS = { Base64EL.class, FileEL.class, JvmEL.class, MathEL.class, RuntimeEL.class, StringEL.class, PipelineEL.class }; private static final ELDefinitionExtractor EXTRACTOR = new ELDefinitionExtractor() {}; public static ELDefinitionExtractor get() { return EXTRACTOR; } public List<ErrorMessage> validateConstants(Class[] classes, Object contextMsg) { return validateConstants(ImmutableSet.<Class>builder().add(DEFAULT_EL_DEFS).add(classes).build(), contextMsg); } public List<ElConstantDefinition> extractConstants(Class[] classes, Object contextMsg) { return extractConstants(ImmutableSet.<Class>builder().add(DEFAULT_EL_DEFS).add(classes).build(), contextMsg); } public List<ErrorMessage> validateFunctions(Class[] classes, Object contextMsg) { return validateFunctions(ImmutableSet.<Class>builder().add(DEFAULT_EL_DEFS).add(classes).build(), contextMsg); } public List<ElFunctionDefinition> extractFunctions(Class[] classes, Object contextMsg) { return extractFunctions(ImmutableSet.<Class>builder().add(DEFAULT_EL_DEFS).add(classes).build(), contextMsg); } public Map<String, ElFunctionDefinition> getElFunctionsCatalog() { return elFunctionsIdx; } public Map<String, ElConstantDefinition> getELConstantsCatalog() { return elConstantsIdx; } private AtomicInteger indexCounter; private final Map<Method, ElFunctionDefinition> elFunctions; private final Map<Field, ElConstantDefinition> elConstants; private final Map<String, ElFunctionDefinition> elFunctionsIdx; private final Map<String, ElConstantDefinition> elConstantsIdx; private ELDefinitionExtractor() { indexCounter = new AtomicInteger(); elFunctions = new ConcurrentHashMap<>(); elConstants = new ConcurrentHashMap<>(); elFunctionsIdx = new ConcurrentHashMap<>(); elConstantsIdx = new ConcurrentHashMap<>(); } private final static Pattern VALID_NAME = Pattern.compile("\\w*"); private List<ErrorMessage> validateFunctions(Set<Class> augmentedClasses, Object contextMsg) { List<ErrorMessage> errors = new ArrayList<>(); for (Class<?> klass : augmentedClasses) { for (Method method : klass.getDeclaredMethods()) { if (method.getAnnotation(ElFunction.class) != null) { if (!Modifier.isPublic(method.getModifiers())) { errors.add(new ErrorMessage(DefinitionError.DEF_050, contextMsg, klass.getSimpleName(), method.getName())); } } } for (Method method : klass.getMethods()) { ElFunctionDefinition fDef = elFunctions.get(method); if (fDef == null) { ElFunction fAnnotation = method.getAnnotation(ElFunction.class); if (fAnnotation != null) { String fName = fAnnotation.name(); if (fName.isEmpty()) { errors.add(new ErrorMessage(DefinitionError.DEF_051, contextMsg, klass.getSimpleName(), method.getName())); } if (!VALID_NAME.matcher(fName).matches()) { errors.add(new ErrorMessage(DefinitionError.DEF_053, contextMsg, klass.getSimpleName(), method.getName(), fName)); } String fPrefix = fAnnotation.prefix(); if (!VALID_NAME.matcher(fPrefix).matches()) { errors.add(new ErrorMessage(DefinitionError.DEF_054, contextMsg, klass.getSimpleName(), method.getName(), fPrefix)); } if (!fPrefix.isEmpty()) { fName = fPrefix + ":" + fName; } if (!Modifier.isStatic(method.getModifiers())) { errors.add(new ErrorMessage(DefinitionError.DEF_052, contextMsg, klass.getSimpleName(), fName)); } Annotation[][] pAnnotations = method.getParameterAnnotations(); Class<?>[] pTypes = method.getParameterTypes(); for (int i = 0; i < pTypes.length; i++) { if (getParamAnnotation(pAnnotations[i]) == null) { errors.add(new ErrorMessage(DefinitionError.DEF_055, contextMsg, klass.getSimpleName(), fName, i)); } } } } } } return errors; } private ElParam getParamAnnotation(Annotation[] paramAnnotations) { for (Annotation annotation : paramAnnotations) { if (annotation instanceof ElParam) { return (ElParam) annotation; } } return null; } List<ElFunctionDefinition> extractFunctions(Set<Class> augmentedClasses, Object contextMsg) { List<ErrorMessage> errors = validateFunctions(augmentedClasses, contextMsg); if (errors.isEmpty()) { List<ElFunctionDefinition> fDefs = new ArrayList<>(); for (Class<?> klass : augmentedClasses) { for (Method method : klass.getMethods()) { ElFunctionDefinition fDef = elFunctions.get(method); if (fDef == null) { ElFunction fAnnotation = method.getAnnotation(ElFunction.class); if (fAnnotation != null) { String fName = fAnnotation.name(); if (!fAnnotation.prefix().isEmpty()) { fName = fAnnotation.prefix() + ":" + fName; } Annotation[][] pAnnotations = method.getParameterAnnotations(); Class<?>[] pTypes = method.getParameterTypes(); List<ElFunctionArgumentDefinition> fArgDefs = new ArrayList<>(pTypes.length); for (int i = 0; i < pTypes.length; i++) { fArgDefs.add(new ElFunctionArgumentDefinition(getParamAnnotation(pAnnotations[i]).value(), pTypes[i].getSimpleName())); } fDef = new ElFunctionDefinition(Integer.toString(indexCounter.incrementAndGet()), fAnnotation.prefix(), fName, fAnnotation.description(), fArgDefs, method.getReturnType().getSimpleName(), method); elFunctionsIdx.put(fDef.getIndex(), fDef); elFunctions.put(method, fDef); } } if (fDef != null) { fDefs.add(fDef); } } } return fDefs; } else { throw new IllegalArgumentException(Utils.format("Invalid EL functions definition: {}", errors)); } } private List<ErrorMessage> validateConstants(Set<Class> augmentedClasses, Object contextMsg) { List<ErrorMessage> errors = new ArrayList<>(); for (Class<?> klass : augmentedClasses) { for (Field field : klass.getDeclaredFields()) { if (field.getAnnotation(ElConstant.class) != null) { if (!Modifier.isPublic(field.getModifiers())) { errors.add(new ErrorMessage(DefinitionError.DEF_060, contextMsg, klass.getSimpleName(), field.getName())); } } } for (Field field : klass.getFields()) { ElConstantDefinition cDef = elConstants.get(field); if (cDef == null) { ElConstant cAnnotation = field.getAnnotation(ElConstant.class); if (cAnnotation != null) { String cName = cAnnotation.name(); if (cName.isEmpty()) { errors.add(new ErrorMessage(DefinitionError.DEF_061, contextMsg, klass.getSimpleName(), field.getName())); } if (!VALID_NAME.matcher(cName).matches()) { errors.add(new ErrorMessage(DefinitionError.DEF_063, contextMsg, klass.getSimpleName(), field.getName(), cName)); } if (!Modifier.isStatic(field.getModifiers())) { errors.add(new ErrorMessage(DefinitionError.DEF_062, contextMsg, klass.getSimpleName(), cName)); } } } } } return errors; } List<ElConstantDefinition> extractConstants(Set<Class> augmentedClasses, Object contextMsg) { List<ErrorMessage> errors = validateConstants(augmentedClasses, contextMsg); if (errors.isEmpty()) { List<ElConstantDefinition> cDefs = new ArrayList<>(); for (Class<?> klass : augmentedClasses) { for (Field field : klass.getFields()) { ElConstantDefinition cDef = elConstants.get(field); if (cDef == null) { ElConstant cAnnotation = field.getAnnotation(ElConstant.class); if (cAnnotation != null) { String cName = cAnnotation.name(); Object value; try { value = field.get(null); } catch (IllegalAccessException ex) { throw new IllegalArgumentException(Utils.format("{}, could not retrieve constant '{}' value: {}", contextMsg, cName, ex.toString(), ex)); } cDef = new ElConstantDefinition(Integer.toString(indexCounter.incrementAndGet()), cName, cAnnotation.description(), field.getType().getSimpleName(), value); elConstantsIdx.put(cDef.getIndex(), cDef); elConstants.put(field, cDef); } } if (cDef != null) { cDefs.add(cDef); } } } return cDefs; } else { throw new IllegalArgumentException(Utils.format("Invalid EL constants definition: {}", errors)); } } }