org.appng.api.support.ConfigValidator Maven / Gradle / Ivy
/*
* 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 extends Param> 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