All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.appng.api.support.ConfigValidator Maven / Gradle / Ivy

There is a newer version: 1.24.5
Show newest version
/*
 * Copyright 2011-2021 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.appng.api.support;

import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.el.ELContext;
import javax.el.ELException;
import javax.el.ValueExpression;
import javax.el.VariableMapper;
import javax.xml.namespace.QName;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.appng.api.ApplicationConfigProvider;
import org.appng.api.InvalidConfigurationException;
import org.appng.api.ParameterSupport;
import org.appng.api.PermissionOwner;
import org.appng.api.XPathProcessor;
import org.appng.api.model.Resource;
import org.appng.api.model.ResourceType;
import org.appng.api.model.Resources;
import org.appng.el.ExpressionEvaluator;
import org.appng.xml.application.ApplicationInfo;
import org.appng.xml.application.Permission;
import org.appng.xml.application.PermissionRef;
import org.appng.xml.application.Permissions;
import org.appng.xml.application.Properties;
import org.appng.xml.application.Property;
import org.appng.xml.application.Role;
import org.appng.xml.application.Roles;
import org.appng.xml.platform.Action;
import org.appng.xml.platform.ActionRef;
import org.appng.xml.platform.ApplicationRootConfig;
import org.appng.xml.platform.Bean;
import org.appng.xml.platform.BeanOption;
import org.appng.xml.platform.Condition;
import org.appng.xml.platform.Datasource;
import org.appng.xml.platform.DatasourceRef;
import org.appng.xml.platform.Event;
import org.appng.xml.platform.FieldDef;
import org.appng.xml.platform.FieldType;
import org.appng.xml.platform.GetParams;
import org.appng.xml.platform.Link;
import org.appng.xml.platform.Linkmode;
import org.appng.xml.platform.Linkpanel;
import org.appng.xml.platform.MetaData;
import org.appng.xml.platform.PageConfig;
import org.appng.xml.platform.PageDefinition;
import org.appng.xml.platform.Param;
import org.appng.xml.platform.Params;
import org.appng.xml.platform.PostParams;
import org.appng.xml.platform.SectionDef;
import org.appng.xml.platform.SectionelementDef;
import org.appng.xml.platform.UrlParams;
import org.appng.xml.platform.UrlSchema;
import org.appng.xml.platform.ValidationGroups.Group;
import org.springframework.util.ClassUtils;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import lombok.extern.slf4j.Slf4j;

/**
 * Validates a {@link ApplicationConfigProvider}, which means it checks that each reference to a 
*
    *
  • page
  • *
  • datasource
  • *
  • event
  • *
  • action
  • *
  • parameter
  • *
* is valid. * * @author Matthias Müller */ @Slf4j public class ConfigValidator { private ApplicationConfigProvider provider; private Set errors; private List detailedErrors; private Set warnings; private boolean throwException = false; private boolean clearErrors = true; private boolean withDetailedErrors = false; private Set permissionNames = new HashSet<>(); public ConfigValidator(ApplicationConfigProvider provider, boolean throwException, boolean clearErrors) { this(provider); this.throwException = throwException; this.clearErrors = clearErrors; } public ConfigValidator(ApplicationConfigProvider provider) { this.provider = provider; this.errors = new HashSet<>(); this.warnings = new HashSet<>(); this.detailedErrors = new ArrayList<>(); } public void validate(String applicationName) throws InvalidConfigurationException { validate(applicationName, Thread.currentThread().getContextClassLoader()); } public void validate(String applicationName, ClassLoader classLoader) throws InvalidConfigurationException { readPermissions(); validateApplicationInfo(); validateApplication(applicationName, classLoader); } public void setWithDetailedErrors(boolean withDetails) { this.withDetailedErrors = withDetails; } private void validateAction(String eventId, Action action) { String resourceName = provider.getResourceNameForEvent(eventId); String actionOrigin = resourceName + ": action '" + action.getId() + "':"; Resource resource = getResourceIfPresent(ResourceType.XML, resourceName); String xpathBase = "//action[@id='" + action.getId() + "']"; validateConditionForAction(resource, action.getId(), action.getConfig().getParams(), action.getCondition(), actionOrigin, xpathBase + "/condition"); checkPermissions(new PermissionOwner(action), actionOrigin, resource, xpathBase + "/config"); DatasourceRef datasourceRef = action.getDatasource(); if (null != datasourceRef) { String dsId = datasourceRef.getId(); validateConditionForAction(resource, action.getId(), action.getConfig().getParams(), datasourceRef.getCondition(), actionOrigin + ", datasource '" + dsId + "'", xpathBase + "/datasource/condition"); Datasource datasource = provider.getDatasource(dsId); if (null == datasource) { String error = actionOrigin + " references the unknown datasource '" + dsId + "'."; addDetailedError(error, resource, xpathBase + "/datasource"); } else { Params params = action.getConfig().getParams(); Map parameterMap = getParameterMap(params == null ? null : params.getParam()); validateDataSourceParameters(actionOrigin, datasourceRef, parameterMap, resourceName); } } } private void validateApplication(String applicationName, ClassLoader classLoader) throws InvalidConfigurationException { long start = System.currentTimeMillis(); validateApplicationRootConfig(); validateDataSources(classLoader); validateActions(); Map pageMap = provider.getPages(); for (String pageId : pageMap.keySet()) { Resource resource = getResourceIfPresent(ResourceType.XML, provider.getResourceNameForPage(pageId)); String origin = getPagePrefix(pageId); PageDefinition page = provider.getPage(pageId); PageConfig config = page.getConfig(); checkPermissions(new PermissionOwner(page), origin, resource, "//page[@id='" + pageId + "']/config"); int i = 1; for (SectionDef sectionDef : page.getStructure().getSection()) { String hidden = sectionDef.getHidden(); validateExpressionForPage(resource, config.getUrlSchema(), hidden, pageId, origin + " section[" + i + "]", "hidden", "//section[" + i + "]"); List elements = sectionDef.getElement(); int j = 1; for (SectionelementDef sectionelement : elements) { String folded = sectionelement.getFolded(); validateExpressionForPage(resource, config.getUrlSchema(), folded, pageId, origin + " section[" + i + "]/element[" + j + "]", "folded", "//section[" + i + "]/element[" + j + "]"); String passive = sectionelement.getPassive(); validateExpressionForPage(resource, config.getUrlSchema(), passive, pageId, origin + " section[" + i + "]/element[" + j + "]", "passive", "//section[" + i + "]/element[" + j + "]"); ActionRef actionRef = sectionelement.getAction(); DatasourceRef datasourceRef = sectionelement.getDatasource(); if (null != actionRef) { String xpathBase = "//action[@id='" + actionRef.getId() + "']"; String actionOrigin = origin + ", action '" + actionRef.getId() + "'"; validateIncludeConditionForPage(resource, config.getUrlSchema(), actionRef.getCondition(), pageId, actionOrigin, xpathBase); checkPermissions(new PermissionOwner(actionRef), actionOrigin, resource, xpathBase); String eventId = actionRef.getEventId(); Event event = provider.getEvent(eventId); if (null == event) { String message = origin + " references the unknown event '" + eventId + "'."; addDetailedError(message, resource, xpathBase + "[@eventId='" + eventId + "']"); } else { String actionId = actionRef.getId(); if (!provider.getActions(eventId).containsKey(actionId)) { String message = getPagePrefix(pageId); message += " references the unknown action '" + actionId + "' (from event '" + eventId + "')."; addDetailedError(message, resource, xpathBase + "[@eventId='" + eventId + "']"); } else { validateActionParameters(pageId, actionRef); // validateActionRef(pageId, actionRef); } } } else if (null != datasourceRef) { String dsId = datasourceRef.getId(); String xpathBase = "//datasource[@id='" + dsId + "']"; String dsOrigin = origin + ", datasource '" + dsId + "'"; validateIncludeConditionForPage(resource, config.getUrlSchema(), datasourceRef.getCondition(), pageId, dsOrigin, xpathBase); checkPermissions(new PermissionOwner(datasourceRef), dsOrigin, resource, xpathBase); Datasource datasource = provider.getDatasource(dsId); if (null == datasource) { String message = getPagePrefix(pageId); message += " references the unknown datasource '" + dsId + "'."; addDetailedError(message, resource, xpathBase); } else { validateDataSourceParameters(origin, datasourceRef, getAllPageParams(page), provider.getResourceNameForPage(pageId)); } } else { // invalid } j++; } i++; } } LOGGER.info("validated application '{}' in {}ms", applicationName, System.currentTimeMillis() - start); processErrors(applicationName); } private Map getAllPageParams(PageDefinition page) { UrlSchema urlSchema = page.getConfig().getUrlSchema(); Map params = new HashMap<>(); if (null != urlSchema) { PostParams postParams = urlSchema.getPostParams(); if (null != postParams) { params.putAll(getParameterMap(postParams.getParamList())); } GetParams getParams = urlSchema.getGetParams(); if (null != getParams) { params.putAll(getParameterMap(getParams.getParamList())); } UrlParams urlParams = urlSchema.getUrlParams(); if (null != urlParams) { params.putAll(getParameterMap(urlParams.getParamList())); } } return params; } private Map getPageGetParams(PageDefinition page) { UrlSchema urlSchema = page.getConfig().getUrlSchema(); Map params = new HashMap<>(); if (null != urlSchema) { GetParams getParams = urlSchema.getGetParams(); if (null != getParams) { params.putAll(getParameterMap(getParams.getParamList())); } } return params; } private void validateApplicationRootConfig() { ApplicationRootConfig applicationRootConfig = provider.getApplicationRootConfig(); if (null == applicationRootConfig) { if (!provider.getPages().isEmpty()) { this.errors.add("No found, application will not work!"); return; } } else { validateLinkPanel(applicationRootConfig.getNavigation(), "application-info.xml:", provider.getResourceNameForApplicationRootConfig(), "//navigation/"); } } private void validateLinkPanel(Linkpanel linkpanel, String origin, String originResourceName, String linkpanelXPath) { if (null != linkpanel) { Resource resource = getResourceIfPresent(ResourceType.XML, originResourceName); checkPermissions(new PermissionOwner(linkpanel), origin + " linkpanel '" + linkpanel.getId() + "'", resource, linkpanelXPath); for (Link link : linkpanel.getLinks()) { String target = link.getTarget(); String linkOrigin = origin + " linkpanel '" + linkpanel.getId() + "' link '" + target + "'"; String xpathBase = "/link[@target='" + target + "']"; Linkmode mode = link.getMode(); if (Linkmode.INTERN.equals(mode)) { String pageName = null; if (StringUtils.isNotBlank(target)) { if (target.startsWith("/")) { int endIdx = target.indexOf("/", 1); pageName = target.substring(1, endIdx > 0 ? endIdx : target.length()); endIdx = pageName.indexOf('?'); if (endIdx > 0) { pageName = pageName.substring(0, endIdx); } } else if (!target.startsWith("?") && !target.startsWith("$")) { String message = linkOrigin + " points to an invalid target, must start with '/', '${}' or '?'!"; addDetailedError(message, resource, linkpanelXPath + xpathBase); } } if (null != pageName && !pageName.startsWith("$")) { PageDefinition page = provider.getPage(pageName); if (page == null) { String message = linkOrigin + " points to the unknown page '" + pageName + "'"; addDetailedError(message, resource, linkpanelXPath + xpathBase); } else if (StringUtils.isNotBlank(target)) { List queryParameters = new ArrayList<>(); int idx = target.indexOf('?'); if (idx > 0) { String query = target.substring(idx + 1); for (String pair : query.split("&")) { int eqIdx = pair.indexOf("="); queryParameters.add(pair.substring(0, eqIdx)); } } Map allGetParams = getPageGetParams(page); Set getParamNames = allGetParams.keySet(); @SuppressWarnings("rawtypes") Collection unknownGetParams = CollectionUtils.subtract(queryParameters, getParamNames); if (!unknownGetParams.isEmpty()) { String message = linkOrigin + " points to page '" + pageName + "' and uses the unknown get-paramter(s) " + unknownGetParams + ". Valid get-parameters are: " + getParamNames; addDetailedError(message, resource, linkpanelXPath + xpathBase); } } } } checkPermissions(new PermissionOwner(link), linkOrigin, resource, linkpanelXPath + xpathBase); } } } private void validateApplicationInfo() { Set rolesNames = new HashSet<>(); Resource resource = getApplicationXml(); checkApplicationProperties(); Roles roles = provider.getApplicationInfo().getRoles(); if (null != roles) { for (Role role : roles.getRole()) { if (!rolesNames.add(role.getName())) { String message = ResourceType.APPLICATION_XML_NAME + ": Duplicate role: " + role.getName(); addDetailedError(message, resource, "//role/name[text()='" + role.getName() + "']"); } Set rolePermissions = new HashSet<>(); for (PermissionRef permissionRef : role.getPermission()) { String permissionId = permissionRef.getId(); String xpath = "//role[name[text()='" + role.getName() + "']]/permission[@id='" + permissionId + "']"; if (!permissionNames.contains(permissionId)) { String message = ResourceType.APPLICATION_XML_NAME + ": The role '" + role.getName() + "' references the unknown permission '" + permissionId + "'."; addDetailedError(message, resource, xpath); } else if (!rolePermissions.add(permissionId)) { String message = ResourceType.APPLICATION_XML_NAME + ": The role '" + role.getName() + "' references the permission '" + permissionId + "' more than once!"; addDetailedError(message, resource, xpath); } } } } } private Resource getApplicationXml() { return getResourceIfPresent(ResourceType.APPLICATION, ResourceType.APPLICATION_XML_NAME); } private Resource getResourceIfPresent(ResourceType type, String resourceName) { Resources resources = provider.getResources(); if (null != resources) { return resources.getResource(type, resourceName); } return null; } private void readPermissions() { ApplicationInfo applicationInfo = provider.getApplicationInfo(); if (null != applicationInfo) { Permissions permissions = applicationInfo.getPermissions(); if (null != permissions) { List permissionList = permissions.getPermission(); Resource resource = getApplicationXml(); for (Permission permission : permissionList) { if (!this.permissionNames.add(permission.getId())) { String message = ResourceType.APPLICATION_XML_NAME + ": Duplicate permission '" + permission.getId() + "'."; addDetailedError(message, resource, "//permissions/permission[@id='" + permission.getId() + "']"); } } } } } private void checkApplicationProperties() { ApplicationInfo applicationInfo = provider.getApplicationInfo(); Resource resource = getApplicationXml(); if (null != applicationInfo) { Properties properties = applicationInfo.getProperties(); Set propertyIds = new HashSet<>(); for (Property p : properties.getProperty()) { if (!propertyIds.add(p.getId())) { String message = ResourceType.APPLICATION_XML_NAME + ": Duplicate property '" + p.getId() + "'."; addDetailedError(message, resource, "//properties/property[@id='" + p.getId() + "']"); } } } } private void checkPermissions(PermissionOwner owner, String origin, Resource resource, String xpathBase) { Collection permissions = owner.getPermissions(); if (null != permissions) { for (org.appng.xml.platform.Permission permission : permissions) { String permName = permission.getRef(); if (!permName.startsWith(DefaultPermissionProcessor.PREFIX_ANONYMOUS) && !permissionNames.contains(permName)) { String message = origin + " references the unknown permission '" + permName + "'."; addDetailedError(message, resource, xpathBase + "/permissions/permission[@ref='" + permName + "']"); } } } } private void validateConditionForAction(Resource origin, String actionId, Params params, Condition condition, String originName, String xpath) { if (null != condition && StringUtils.isNotBlank(condition.getExpression())) { if (!condition.getExpression().startsWith("${") || !condition.getExpression().endsWith("}")) { addDetailedError(originName + " invalid condition '" + condition.getExpression() + "'", origin, xpath); } else { Map variables = getVariables(params); String message = originName + " condition: '" + condition.getExpression() + "' is invalid: "; ExpressionEvaluator ee = getExpressionEvaluator("action", actionId, variables); validateCondition(origin, ee, condition, xpath, message); } } } private Map getVariables(Params params) { Map variables = new HashMap<>(); if (params != null) { params.getParam().forEach(p -> variables.put(p.getName(), p.getValue())); } return variables; } private void validateIncludeConditionForPage(Resource pageResource, UrlSchema params, Condition condition, String pageId, String originName, String xpath) { if (null != condition && StringUtils.isNotBlank(condition.getExpression())) { if (!condition.getExpression().startsWith("${") || !condition.getExpression().endsWith("}")) { addDetailedError(originName + " invalid condition '" + condition.getExpression() + "'", pageResource, xpath); } else { ExpressionEvaluator ee = getPageExpressionEvaluator(params, pageId); String message = originName + " condition: '" + condition.getExpression() + "' is invalid: "; validateCondition(pageResource, ee, condition, xpath, message); } } } private ExpressionEvaluator getPageExpressionEvaluator(UrlSchema params, String pageId) { Map variables = new HashMap<>(); if (null != params.getGetParams()) { addParams(variables, params.getGetParams().getParamList()); } if (null != params.getPostParams()) { addParams(variables, params.getPostParams().getParamList()); } if (null != params.getUrlParams()) { addParams(variables, params.getUrlParams().getParamList()); } return getExpressionEvaluator("page", pageId, variables); } private void addParams(Map variables, List paramList) { if (null != paramList) { paramList.forEach(p -> variables.put(p.getName(), p.getValue())); } } private void validateExpressionForPage(Resource resource, UrlSchema urlSchema, String expression, String pageId, String origin, String attribute, String xpath) { if (StringUtils.isNotBlank(expression) && !("true".equalsIgnoreCase(expression) || "false".equalsIgnoreCase(expression))) { ExpressionEvaluator ee = getPageExpressionEvaluator(urlSchema, pageId); String message = origin + " attribute '" + attribute + "': "; validateCondition(resource, ee, expression, xpath, message); } } private ExpressionEvaluator getExpressionEvaluator(String type, String id, Map variables) { VariableMapper variableMapper = new VariableMapper() { public ValueExpression setVariable(String variable, ValueExpression expression) { return null; } public ValueExpression resolveVariable(String variable) { if (!variables.containsKey(variable)) { throw new ELException("parameter '" + variable + "' not found on " + type + " '" + id + "'"); } return new ValueExpression() { public boolean isLiteralText() { return false; } public int hashCode() { return 0; } public String getExpressionString() { return null; } public boolean equals(Object obj) { return false; } public void setValue(ELContext context, Object value) { } public boolean isReadOnly(ELContext context) { return false; } public Object getValue(ELContext context) { return variables.get(variable); } public Class getType(ELContext context) { return null; } public Class getExpectedType() { return null; } }; } }; ExpressionEvaluator ee = new ExpressionEvaluator(variableMapper); return ee; } public final void processErrors(String applicationName) throws InvalidConfigurationException { if (getWarnings().size() > 0 || getErrors().size() > 0) { if (!getWarnings().isEmpty()) { StringBuilder sb = appendAndClear("found warnings:", getWarnings()); LOGGER.warn(sb.toString()); } if (!getErrors().isEmpty()) { StringBuilder sb = appendAndClear("found errors:", getErrors()); if (throwException) { throw new InvalidConfigurationException(applicationName, sb.toString()); } else { LOGGER.error(sb.toString()); } } } else { LOGGER.info("validation returned no errors and no warnings"); } } private StringBuilder appendAndClear(String start, Set messages) { List sorted = new ArrayList<>(messages); Collections.sort(sorted); StringBuilder sb = new StringBuilder(start); sorted.forEach(warning -> { sb.append("\r\n"); sb.append(warning); }); if (clearErrors) { messages.clear(); } return sb; } public void validateDataSources(ClassLoader classLoader) { Set keySet = provider.getDataSources().keySet(); for (String id : keySet) { String resourceName = provider.getResourceNameForDataSource(id); String messagePrefix = resourceName + ": datasource '" + id + "':"; Datasource datasource = provider.getDatasource(id); String xpathBase = "//datasource[@id='" + id + "']/config/"; Resource resource = getResourceIfPresent(ResourceType.XML, resourceName); checkPermissions(new PermissionOwner(datasource), messagePrefix, resource, xpathBase); List linkpanels = datasource.getConfig().getLinkpanel(); for (Linkpanel linkpanel : linkpanels) { validateLinkPanel(linkpanel, messagePrefix, resourceName, xpathBase + "linkpanel/"); } MetaData metaData = datasource.getConfig().getMetaData(); if (StringUtils.isBlank(metaData.getBindClass())) { String message = messagePrefix + " no bindclass given!"; addDetailedError(message, resource, xpathBase + "meta-data"); } else { Map variables = getVariables(datasource.getConfig().getParams()); Object bindObject = getBindObject(classLoader, metaData.getBindClass()); if (null != bindObject) { variables.put("current", bindObject); } ExpressionEvaluator ee = getExpressionEvaluator("datasource", id, variables); for (FieldDef fieldDef : metaData.getFields()) { String fieldXpath = xpathBase + "meta-data/field[@name='" + fieldDef.getName() + "']"; Condition condition = fieldDef.getCondition(); if (null != condition) { String xpath = fieldXpath + "/condition"; String message = messagePrefix + " field: " + fieldDef.getName() + ", condition: '" + condition.getExpression() + "' is invalid: "; validateCondition(resource, ee, condition, xpath, message); } String hiddenMessage = messagePrefix + " field: " + fieldDef.getName() + ", hidden: '" + fieldDef.getHidden() + "' is invalid: "; validateCondition(resource, ee, fieldDef.getHidden(), fieldXpath, hiddenMessage); String readOnlyMessage = messagePrefix + " field: " + fieldDef.getName() + ", readOnly: '" + fieldDef.getReadonly() + "' is invalid: "; validateCondition(resource, ee, fieldDef.getReadonly(), fieldXpath, readOnlyMessage); } } } } private Object getBindObject(ClassLoader classLoader, String bindClassName) { try { Class bindClass = ClassUtils.forName(bindClassName, classLoader); Class enclosingClass = bindClass.getEnclosingClass(); int modifier = bindClass.getModifiers(); if (bindClass.isInterface()) { return Proxy.newProxyInstance(classLoader, new Class[] { bindClass }, new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null; } }); } else if (enclosingClass == null || Modifier.isStatic(modifier)) { return bindClass.newInstance(); } else if (Modifier.isPublic(modifier)) { return bindClass.getConstructor(enclosingClass).newInstance(enclosingClass.newInstance()); } } catch (ReflectiveOperationException o) { String errorMssg = String.format( "could not create instance of '%s', is it an interface or default-constructor missing?", bindClassName); LOGGER.debug(errorMssg); } return null; } private void validateCondition(Resource resource, ExpressionEvaluator ee, Condition condition, String xpath, String message) { validateCondition(resource, ee, condition.getExpression(), xpath, message); } private void validateCondition(Resource resource, ExpressionEvaluator ee, String expression, String xpath, String message) { if (StringUtils.isNotBlank(expression)) { try { ee.evaluate(expression); } catch (ELException ele) { message += ele.getMessage(); addDetailedError(message, resource, xpath); } } } public void validateActions() { Set eventIds = provider.getEventIds(); for (String eventId : eventIds) { Event event = provider.getEvent(eventId); String origin = provider.getResourceNameForEvent(eventId); Resource resource = getResourceIfPresent(ResourceType.XML, origin); checkPermissions(new PermissionOwner(event), origin + ": event '" + eventId + "'", resource, "//event[@id='" + eventId + "']/config"); for (Action action : event.getActions()) { validateAction(eventId, action); } } } public void validateMetaData(URLClassLoader classLoader) { if (null != classLoader) { Set keySet = provider.getDataSources().keySet(); for (String id : keySet) { String resourceName = provider.getResourceNameForDataSource(id); String messagePrefix = resourceName + ": datasource '" + id + "':"; Datasource datasource = provider.getDatasource(id); MetaData metaData = datasource.getConfig().getMetaData(); checkBindClass(classLoader, id, resourceName, messagePrefix, metaData); checkValidationGroupClass(classLoader, id, resourceName, messagePrefix, metaData); } } } private void checkValidationGroupClass(URLClassLoader classLoader, String id, String resourceName, String messagePrefix, MetaData metaData) throws LinkageError { if (null != metaData.getValidation()) { List validationGroups = metaData.getValidation().getGroups(); for (Group group : validationGroups) { String validationGroupClassName = group.getClazz(); try { // we just try to get the class from the class loader. // If it throws an exception we will create an error message and maybe a detailed error @SuppressWarnings("unused") Class validationGroupClass = ClassUtils.forName(validationGroupClassName, classLoader); } catch (ClassNotFoundException e) { String message = messagePrefix + " validation group class '" + validationGroupClassName + "' not found!"; addDetailedError(message, getResourceIfPresent(ResourceType.XML, resourceName), "//datasource[@id='" + id + "']/config/meta-data/validation/group[@class='" + validationGroupClassName + "']"); } } } } private void checkBindClass(URLClassLoader classLoader, String id, String resourceName, String messagePrefix, MetaData metaData) throws LinkageError { String xpath = "//datasource[@id='" + id + "']/config/meta-data"; String bindClassName = metaData.getBindClass(); try { Class bindClass = ClassUtils.forName(bindClassName, classLoader); ClassWrapper wrapper = new ClassWrapper(bindClass); List fields = metaData.getFields(); String binding = metaData.getBinding(); for (FieldDef fieldDef : fields) { if (!FieldType.LINKPANEL.equals(fieldDef.getType())) { String name = fieldDef.getName(); String property = fieldDef.getBinding(); if (property == null) { property = (binding == null ? "" : binding + ".") + name; } if (!wrapper.isReadableProperty(property)) { String message = messagePrefix + " property '" + property + "' of class '" + bindClassName + "' is not readable!"; getWarnings().add(message); // addDetailedError(message, getResourceIfPresent(ResourceType.XML, resourceName), // xpath + "/field[@name='" + name + "']"); } if (!"true".equals(fieldDef.getReadonly())) { boolean isWritable = wrapper.isWritableProperty(property); if (!isWritable) { String message = messagePrefix + " property '" + property + "' of class '" + bindClassName + "' is not writable!"; getWarnings().add(message); } } } } } catch (ClassNotFoundException e) { String message = messagePrefix + " bindclass '" + bindClassName + "' not found!"; addDetailedError(message, getResourceIfPresent(ResourceType.XML, resourceName), xpath); } } private void validateDataSourceParameters(String origin, DatasourceRef datasourceRef, Map availableParams, String resourceName) { String dsId = datasourceRef.getId(); Datasource datasource = provider.getDatasource(dsId); Params refParams = datasourceRef.getParams(); Params dataSourceParams = datasource.getConfig().getParams(); String type = "dataSource"; String dsResource = provider.getResourceNameForDataSource(dsId); Map paramsFromDatasource = checkReferenceParameters(origin, dsId, refParams, dataSourceParams, type, dsResource, availableParams, resourceName); ParameterSupport dsParameterSupport = new DollarParameterSupport(paramsFromDatasource); Bean bean = datasource.getBean(); if (null == bean) { LOGGER.debug("datasource '{}' is static", datasource.getId()); return; } validateBeanOptions(dsResource + ": datasource '" + dsId + "'", dataSourceParams, dsParameterSupport, bean, dsResource, "datasource", dsId); } private Map checkReferenceParameters(String origin, String refId, Params refParams, Params execParams, String type, String resourceName, Map availableParams, String resourceFileName) { Map paramsFromRef = getParameterMap(refParams == null ? null : refParams.getParam()); Map paramsFromExec = getParameterMap(execParams == null ? null : execParams.getParam()); Set execParamNames = paramsFromExec.keySet(); Set paramNames = new HashSet<>(); for (String string : paramsFromRef.values()) { if (string.matches("\\$\\{(.)*\\}")) { String param = string.replace("${", "").replace("}", ""); if (StringUtils.isNotBlank(param)) { paramNames.add(param); } } } @SuppressWarnings("unchecked") Collection missingRefParameters = CollectionUtils.subtract(paramNames, availableParams.keySet()); Resource resource = getResourceIfPresent(ResourceType.XML, resourceFileName); String xpathBase = "//" + type.toLowerCase() + "[@id='" + refId + "']/params/"; if (!missingRefParameters.isEmpty()) { for (String missing : missingRefParameters) { // we need the type and id of the origin String message = origin + " the reference to " + type + " '" + refId + "' uses the unknown parameter " + missing + ". Supported parameters are: " + availableParams.keySet(); addDetailedError(message, resource, xpathBase + "param[text()='${" + missing + "}']"); } } @SuppressWarnings("unchecked") Collection unsupportedParameters = CollectionUtils.subtract(paramsFromRef.keySet(), execParamNames); if (!unsupportedParameters.isEmpty()) { for (String unsupported : unsupportedParameters) { String message = origin + " the reference to " + type + " '" + refId + "' (from '" + resourceName + "') uses the parameter " + unsupported + " which is unknown in the " + type + " '" + refId + "'! Supported parameters are: " + execParamNames; addDetailedError(message, resource, xpathBase + "param[@name='" + unsupported + "']"); } } return paramsFromExec; } private void validateBeanOptions(String origin, Params params, ParameterSupport parameterSupport, Bean bean, String resourceName, String originType, String originId) { List options = bean.getOptions(); if (null != options) { for (BeanOption option : options) { Map otherAttributes = option.getOtherAttributes(); for (QName key : otherAttributes.keySet()) { String value = otherAttributes.get(key); List paramNames = parameterSupport.getParameters(value); for (String paramName : paramNames) { boolean found = findParameter(params, paramName); if (!found) { String message = origin + ", option '" + option.getName() + "' references the unknown parameter '" + paramName + "'. Valid parameters are: " + parameterSupport.getParameterNames(); addDetailedError(message, getResourceIfPresent(ResourceType.XML, resourceName), "//" + originType.toLowerCase() + "[@id='" + originId + "']/bean/option[@" + key + "='" + value + "']"); } } } } } } private Map getParameterMap(List params) { Map parameters = new HashMap<>(); if (null != params) { for (Param param : params) { String value = StringUtils.isNotBlank(param.getValue()) ? param.getValue() : StringUtils.isNotBlank(param.getDefault()) ? param.getDefault() : "value"; parameters.put(param.getName(), value); } } return parameters; } private void validateActionParameters(String pageId, ActionRef actionRef) { String eventId = actionRef.getEventId(); String actionId = actionRef.getId(); Action action = provider.getAction(eventId, actionId); String origin = getPagePrefix(pageId); Params params = action.getConfig().getParams(); String resourceName = provider.getResourceNameForEvent(eventId); PageDefinition page = provider.getPage(pageId); Map paramsFromDatasource = checkReferenceParameters(origin, actionId, actionRef.getParams(), params, "action", resourceName, getAllPageParams(page), provider.getResourceNameForPage(pageId)); Bean bean = action.getBean(); if (null != bean && null != bean.getOptions()) { ParameterSupport actionParamMapSupport = new DollarParameterSupport(paramsFromDatasource); String actionOrigin = resourceName + ": action '" + actionId + "'"; validateBeanOptions(actionOrigin, params, actionParamMapSupport, bean, resourceName, "action", actionId); } } private String getPagePrefix(String pageId) { return provider.getResourceNameForPage(pageId) + ": page '" + pageId + "'"; } private void addDetailedError(String error, Resource resource, String xpath) { this.errors.add(error); if (withDetailedErrors) { NodeList nodes = getNodesWithPositionForXpath(resource, xpath); if (null != nodes && nodes.getLength() > 0) { for (int x = 0; x < nodes.getLength(); x++) { ConfigValidationError detailError = new ConfigValidationError(); detailError.setMessage(error); detailError.setLine((Integer) nodes.item(x).getUserData(PositionalXMLReader.LINE_NUMBER_KEY_NAME)); detailError.setResourceName(resource.getName()); detailedErrors.add(detailError); } } else { LOGGER.error("no node found for xpath: {} in resource {}", xpath, resource.getName()); } } } private NodeList getNodesWithPositionForXpath(Resource resource, String xpath) { try { InputStream is = null; if (null != resource.getBytes()) { is = new ByteArrayInputStream(resource.getBytes()); } else if (null != resource.getCachedFile()) { is = new FileInputStream(resource.getCachedFile()); } if (null != is) { Document doc = PositionalXMLReader.readXML(is); XPathProcessor process = new XPathProcessor(doc); NodeList nodes = process.getNodes(xpath); is.close(); return nodes; } else { LOGGER.error("Neither bytes nor cached file is available for resource {}", resource.getName()); return null; } } catch (IOException | SAXException e) { LOGGER.error(String.format("cannot get node with xpath %s", xpath), e); } return null; } private boolean findParameter(Params params, String paramName) { return null != params && findParameter(params.getParam(), paramName); } private boolean findParameter(List paramList, String paramName) { if (null == paramList) { return false; } for (Param param : paramList) { if (param.getName().equals(paramName)) { return true; } } return false; } public Set getErrors() { return errors; } public List getDetaildErrors() { return detailedErrors; } public Set getWarnings() { return warnings; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy