blob: 023c847c4fea0650fb9125f2116924aa9163aea7 [file] [log] [blame]
/*
* Licensed to 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 org.apache.logging.log4j.audit.generator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.audit.util.NamingUtils;
import org.apache.logging.log4j.catalog.api.Attribute;
import org.apache.logging.log4j.catalog.api.CatalogData;
import org.apache.logging.log4j.catalog.api.CatalogReader;
import org.apache.logging.log4j.catalog.api.Constraint;
import org.apache.logging.log4j.catalog.api.ConstraintType;
import org.apache.logging.log4j.catalog.api.Event;
import org.apache.logging.log4j.catalog.api.EventAttribute;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class InterfacesGenerator {
private static final Logger LOGGER = LogManager.getLogger(InterfacesGenerator.class);
private static final String CONSTRAINT_IMPORT = "org.apache.logging.log4j.audit.annotation.Constraint";
private static final String REQUIRED_IMPORT = "org.apache.logging.log4j.audit.annotation.Required";
private static final String CONSTRAINTS_ATTR = ", constraints={";
private static final String CONSTRAINT = "@Constraint(constraintType=\"%s\", constraintValue=\"%s\")";
private static final String KEY = "key=\"";
private static final String REQUIRED_ATTR = "required=true";
private static final String REQUIRED = "@Required";
private static final String REQUEST_CONTEXT_IMPORT = "org.apache.logging.log4j.audit.annotation.RequestContext";
private static final String PARENT_IMPORT = "org.apache.logging.log4j.audit.AuditEvent";
private static final String EVENT_NAME_IMPORT = "org.apache.logging.log4j.audit.annotation.EventName";
private static final String MAX_LENGTH_IMPORT = "org.apache.logging.log4j.audit.annotation.MaxLength";
private static final String REQCTX_ANN = "@RequestContext(";
private static final String PARENT_CLASS = "AuditEvent";
private static final String REQCTX = "ReqCtx_";
private static final String EVENT_ID = "eventID";
private static final String EVENT_TYPE = "eventType";
private static final String TIMESTAMP = "timeStamp";
private static final String CONTEXT = "context";
@Autowired
private CatalogReader catalogReader;
@Value("${packageName:org.apache.logging.log4j.audit.event}")
private String packageName;
@Value("${outputDirectory:target/generated-sources/log4j-audit}")
private String outputDirectory;
@Value("${maxKeyLength:32}")
private int maxKeyLength;
@Value("${enterpriseId:18060}")
private int enterpriseId;
@Value("${verbose:false}")
private boolean verbose;
public CatalogReader getCatalogReader() {
return catalogReader;
}
public void setCatalogReader(CatalogReader catalogReader) {
this.catalogReader = catalogReader;
}
public String getPackageName() {
return packageName;
}
public void setPackageName(String packageName) {
this.packageName = packageName;
}
public String getOutputDirectory() {
return outputDirectory;
}
public void setOutputDirectory(String outputDirectory) {
this.outputDirectory = outputDirectory;
}
public void setMaxKeyLength(int maxKeyLength) {
this.maxKeyLength = maxKeyLength;
}
public void setEnterpriseId(int enterpriseId) {
this.enterpriseId = enterpriseId;
}
public void setVerbose(boolean verbose) {
this.verbose = verbose;
}
public void generateSource() throws Exception {
boolean errors = false;
CatalogData catalogData = catalogReader.read();
if (catalogData != null) {
List<Event> events = catalogData.getEvents();
Map<String, Attribute> requestContextAttrs = new HashMap<>();
Map<String, Boolean> requestContextIsRequired = new HashMap<>();
Map<String, Attribute> attributes = catalogReader.getAttributes();
Map<String, String> importedTypes = new HashMap<>();
boolean anyConstraints = false;
for (Attribute attribute : attributes.values()) {
if (attribute.isRequestContext()) {
String name = attribute.getName();
if (name.startsWith(REQCTX)) {
name = name.substring(REQCTX.length());
}
requestContextAttrs.put(name, attribute);
requestContextIsRequired.put(name, attribute.isRequired());
}
}
for (Event event : events) {
String maxLen = Integer.toString(enterpriseId);
int maxNameLength = maxKeyLength - maxLen.length() - 1;
if (event.getName().length() > maxNameLength) {
LOGGER.error("{} exceeds maximum length of {} for an event name", event.getName(), maxNameLength);
errors = true;
continue;
}
ClassGenerator classGenerator = new ClassGenerator(
NamingUtils.getClassName(event.getName()), outputDirectory);
classGenerator.setClass(false);
classGenerator.setPackageName(packageName);
classGenerator.setParentClassName(PARENT_CLASS);
classGenerator.setJavadocComment(event.getDescription());
classGenerator.setVerbose(verbose);
Set<String> imports = classGenerator.getImports();
imports.add(PARENT_IMPORT);
StringBuilder annotations = new StringBuilder();
imports.add(EVENT_NAME_IMPORT);
annotations.append("@EventName(\"").append(event.getName()).append("\")\n");
imports.add(MAX_LENGTH_IMPORT);
annotations.append("@MaxLength(").append(maxKeyLength).append(")");
List<EventAttribute> eventAttributes = event.getAttributes();
boolean anyRequired = false;
if (eventAttributes != null) {
for (EventAttribute eventAttribute : eventAttributes) {
Attribute attribute = attributes.get(eventAttribute.getName());
if (attribute == null) {
LOGGER.error("Unable to locate attribute name {}", eventAttribute.getName());
errors = true;
continue;
}
if (attribute.isRequestContext() && attribute.isRequired()) {
String name = eventAttribute.getName();
if (name.startsWith(REQCTX)) {
name = name.substring(REQCTX.length());
}
requestContextIsRequired.put(name, Boolean.TRUE);
continue;
}
String name = attribute.getName();
if (EVENT_ID.equals(name) || EVENT_TYPE.equals(name) || TIMESTAMP.equals(name)) {
continue;
}
if (name.indexOf('.') != -1) {
name = name.replaceAll("\\.", "");
}
if (name.indexOf('/') != -1) {
name = name.replaceAll("/", "");
}
if (name.length() > maxKeyLength) {
LOGGER.error("{} exceeds maximum length of {} for an attribute name", name, maxKeyLength);
errors = true;
continue;
}
String type = attribute.getDataType().getTypeName();
MethodDefinition definition = new MethodDefinition("void",
NamingUtils.getMutatorName(name));
if (!attribute.isRequestContext() && attribute.getDataType().getImportClass() != null) {
if (!importedTypes.containsKey(attribute.getDataType().getTypeName())) {
importedTypes.put(attribute.getDataType().getTypeName(), attribute.getDataType().getImportClass());
}
}
definition.addParameter(new Parameter(name, type, attribute.getDescription()));
definition.setInterface(true);
definition.setVisability("public");
definition.setJavadocComments(attribute.getDisplayName()
+ " : " + attribute.getDescription());
StringBuilder buffer = new StringBuilder();
Set<Constraint> constraints = attribute.getConstraints();
boolean first = true;
if (attribute.isRequired() || eventAttribute.isRequired()) {
anyRequired = true;
buffer.append(REQUIRED);
first = false;
}
if (constraints != null && constraints.size() > 0) {
anyConstraints = true;
for (Constraint constraint : constraints) {
if (!first) {
buffer.append("\n ");
}
first = false;
appendConstraint(constraint, buffer);
}
}
if (buffer.length() > 0) {
definition.setAnnotation(buffer.toString());
}
classGenerator.addMethod(definition);
}
}
if (importedTypes.size() > 0) {
imports.addAll(importedTypes.values());
}
if (anyRequired) {
imports.add(REQUIRED_IMPORT);
}
boolean firstReqCtx = true;
if (requestContextAttrs.size() > 0) {
imports.add(REQUEST_CONTEXT_IMPORT);
StringBuilder reqCtx = new StringBuilder();
for (Map.Entry<String, Attribute> entry : requestContextAttrs.entrySet()) {
if (!firstReqCtx) {
reqCtx.append(")\n");
}
firstReqCtx = false;
reqCtx.append(REQCTX_ANN);
reqCtx.append(KEY).append(entry.getKey()).append("\"");
Attribute attrib = entry.getValue();
String name = attrib.getName();
if (name.startsWith(REQCTX)) {
name = name.substring(REQCTX.length());
}
Boolean isRequired = null;
final String attrName = name;
if (event.getAttributes() != null) {
Optional<EventAttribute> optional = event.getAttributes().stream().filter(a -> attrName.equals(a.getName())).findFirst();
if (optional.isPresent()) {
isRequired = optional.get().isRequired();
}
}
if ((isRequired != null && isRequired) ||
(isRequired == null && requestContextIsRequired.get(name))) {
reqCtx.append(", ").append(REQUIRED_ATTR);
}
Set<Constraint> constraints = entry.getValue().getConstraints();
if (constraints != null && constraints.size() > 0) {
anyConstraints = true;
reqCtx.append(CONSTRAINTS_ATTR);
boolean first = true;
for (Constraint constraint : constraints) {
if (!first) {
reqCtx.append(", ");
}
first = false;
appendConstraint(constraint, reqCtx);
}
reqCtx.append("}");
}
}
reqCtx.append(")");
if (annotations.length() > 0) {
annotations.append("\n");
}
annotations.append(reqCtx.toString());
}
if (anyConstraints) {
imports.add(CONSTRAINT_IMPORT);
}
if (annotations.length() > 0) {
classGenerator.setAnnotations(annotations.toString());
}
classGenerator.generate();
}
}
if (errors) {
throw new IllegalStateException("Errors were encountered during code generation");
}
}
void appendConstraint(Constraint constraint, StringBuilder buffer) {
ConstraintType type = constraint.getConstraintType();
// Add the escapes since they have been removed when converting the original data to a Java Strinng. They need to
// be added back for use in the Constraint declaration.
buffer.append(String.format(CONSTRAINT, type.getName(), constraint.getValue().replace("\\", "\\\\")));
}
}