View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.audit.generator;
18  
19  import java.util.HashMap;
20  import java.util.List;
21  import java.util.Map;
22  import java.util.Optional;
23  import java.util.Set;
24  
25  import org.apache.logging.log4j.LogManager;
26  import org.apache.logging.log4j.Logger;
27  import org.apache.logging.log4j.audit.util.NamingUtils;
28  import org.apache.logging.log4j.catalog.api.Attribute;
29  import org.apache.logging.log4j.catalog.api.CatalogData;
30  import org.apache.logging.log4j.catalog.api.CatalogReader;
31  import org.apache.logging.log4j.catalog.api.Constraint;
32  import org.apache.logging.log4j.catalog.api.ConstraintType;
33  import org.apache.logging.log4j.catalog.api.Event;
34  import org.apache.logging.log4j.catalog.api.EventAttribute;
35  import org.springframework.beans.factory.annotation.Autowired;
36  import org.springframework.beans.factory.annotation.Value;
37  import org.springframework.stereotype.Component;
38  
39  @Component
40  public class InterfacesGenerator {
41  
42      private static final Logger LOGGER = LogManager.getLogger(InterfacesGenerator.class);
43  
44      private static final String CONSTRAINT_IMPORT = "org.apache.logging.log4j.audit.annotation.Constraint";
45      private static final String REQUIRED_IMPORT = "org.apache.logging.log4j.audit.annotation.Required";
46      private static final String CONSTRAINTS_ATTR = ", constraints={";
47      private static final String CONSTRAINT = "@Constraint(constraintType=\"%s\", constraintValue=\"%s\")";
48      private static final String KEY = "key=\"";
49      private static final String REQUIRED_ATTR = "required=true";
50      private static final String REQUIRED = "@Required";
51  
52      private static final String REQUEST_CONTEXT_IMPORT = "org.apache.logging.log4j.audit.annotation.RequestContext";
53      private static final String PARENT_IMPORT = "org.apache.logging.log4j.audit.AuditEvent";
54      private static final String MAX_LENGTH_IMPORT = "org.apache.logging.log4j.audit.annotation.MaxLength";
55      private static final String REQCTX_ANN = "@RequestContext(";
56  
57      private static final String PARENT_CLASS = "AuditEvent";
58  
59      private static final String REQCTX = "ReqCtx_";
60  
61      private static final String EVENT_ID = "eventID";
62  
63      private static final String EVENT_TYPE = "eventType";
64  
65      private static final String TIMESTAMP = "timeStamp";
66  
67      private static final String CONTEXT = "context";
68  
69      @Autowired
70      private CatalogReader catalogReader;
71  
72      @Value("${packageName:org.apache.logging.log4j.audit.event}")
73      private String packageName;
74  
75      @Value("${outputDirectory:target/generated-sources/log4j-audit}")
76      private String outputDirectory;
77  
78      @Value("${maxKeyLength:32}")
79      private int maxKeyLength;
80  
81      @Value("${enterpriseId:18060}")
82      private int enterpriseId;
83  
84      @Value("${verbose:false}")
85      private boolean verbose;
86  
87      public CatalogReader getCatalogReader() {
88          return catalogReader;
89      }
90  
91      public void setCatalogReader(CatalogReader catalogReader) {
92          this.catalogReader = catalogReader;
93      }
94  
95      public String getPackageName() {
96          return packageName;
97      }
98  
99      public void setPackageName(String packageName) {
100         this.packageName = packageName;
101     }
102 
103     public String getOutputDirectory() {
104         return outputDirectory;
105     }
106 
107     public void setOutputDirectory(String outputDirectory) {
108         this.outputDirectory = outputDirectory;
109     }
110 
111     public void setMaxKeyLength(int maxKeyLength) {
112         this.maxKeyLength = maxKeyLength;
113     }
114 
115     public void setEnterpriseId(int enterpriseId) {
116         this.enterpriseId = enterpriseId;
117     }
118 
119     public void setVerbose(boolean verbose) {
120         this.verbose = verbose;
121     }
122 
123     public void generateSource() throws Exception {
124         boolean errors = false;
125         CatalogData catalogData = catalogReader.read();
126         if (catalogData != null) {
127             List<Event> events = catalogData.getEvents();
128             Map<String, Attribute> requestContextAttrs = new HashMap<>();
129             Map<String, Boolean> requestContextIsRequired = new HashMap<>();
130             Map<String, Attribute> attributes = catalogReader.getAttributes();
131             Map<String, String> importedTypes = new HashMap<>();
132             boolean anyConstraints = false;
133             for (Attribute attribute : attributes.values()) {
134                 if (attribute.isRequestContext()) {
135                     String name = attribute.getName();
136                     if (name.startsWith(REQCTX)) {
137                         name = name.substring(REQCTX.length());
138                     }
139                     requestContextAttrs.put(name, attribute);
140                     requestContextIsRequired.put(name, attribute.isRequired());
141                 }
142             }
143             for (Event event : events) {
144                 String maxLen = Integer.toString(enterpriseId);
145                 int maxNameLength = maxKeyLength - maxLen.length() - 1;
146                 if (event.getName().length() > maxNameLength) {
147                     LOGGER.error("{} exceeds maximum length of {} for an event name", event.getName(), maxNameLength);
148                     errors = true;
149                     continue;
150                 }
151                 ClassGenerator classGenerator = new ClassGenerator(
152                         NamingUtils.getClassName(event.getName()), outputDirectory);
153                 classGenerator.setClass(false);
154                 classGenerator.setPackageName(packageName);
155                 classGenerator.setParentClassName(PARENT_CLASS);
156                 classGenerator.setJavadocComment(event.getDescription());
157                 classGenerator.setVerbose(verbose);
158                 Set<String> imports = classGenerator.getImports();
159                 imports.add(PARENT_IMPORT);
160                 StringBuilder annotations = new StringBuilder();
161                 imports.add(MAX_LENGTH_IMPORT);
162                 annotations.append("@MaxLength(").append(maxKeyLength).append(")");
163 
164                 List<EventAttribute> eventAttributes = event.getAttributes();
165                 boolean anyRequired = false;
166                 if (eventAttributes != null) {
167                     for (EventAttribute eventAttribute : eventAttributes) {
168                         Attribute attribute = attributes.get(eventAttribute.getName());
169                         if (attribute == null) {
170                             LOGGER.error("Unable to locate attribute name {}", eventAttribute.getName());
171                             errors = true;
172                             continue;
173                         }
174                         if (attribute.isRequestContext() && attribute.isRequired()) {
175                             String name = eventAttribute.getName();
176                             if (name.startsWith(REQCTX)) {
177                                 name = name.substring(REQCTX.length());
178                             }
179                             requestContextIsRequired.put(name, Boolean.TRUE);
180                             continue;
181                         }
182                         String name = attribute.getName();
183 
184                         if (EVENT_ID.equals(name) || EVENT_TYPE.equals(name) || TIMESTAMP.equals(name)) {
185                             continue;
186                         }
187 
188                         if (name.indexOf('.') != -1) {
189                             name = name.replaceAll("\\.", "");
190                         }
191 
192                         if (name.indexOf('/') != -1) {
193                             name = name.replaceAll("/", "");
194                         }
195                         if (name.length() > maxKeyLength) {
196                             LOGGER.error("{} exceeds maximum length of {} for an attribute name", name, maxKeyLength);
197                             errors = true;
198                             continue;
199                         }
200 
201                         String type = attribute.getDataType().getTypeName();
202 
203                         MethodDefinition definition = new MethodDefinition("void",
204                                 NamingUtils.getMutatorName(name));
205                         if (!attribute.isRequestContext() && attribute.getDataType().getImportClass() != null) {
206                             if (!importedTypes.containsKey(attribute.getDataType().getTypeName())) {
207                                 importedTypes.put(attribute.getDataType().getTypeName(), attribute.getDataType().getImportClass());
208                             }
209                         }
210                         definition.addParameter(new Parameter(name, type, attribute.getDescription()));
211                         definition.setInterface(true);
212                         definition.setVisability("public");
213                         definition.setJavadocComments(attribute.getDisplayName()
214                                 + " : " + attribute.getDescription());
215 
216                         StringBuilder buffer = new StringBuilder();
217                         Set<Constraint> constraints = attribute.getConstraints();
218                         boolean first = true;
219                         if (attribute.isRequired() || eventAttribute.isRequired()) {
220                             anyRequired = true;
221                             buffer.append(REQUIRED);
222                             first = false;
223                         }
224                         if (constraints != null && constraints.size() > 0) {
225                             anyConstraints = true;
226                             for (Constraint constraint : constraints) {
227                                 if (!first) {
228                                     buffer.append("\n    ");
229                                 }
230                                 first = false;
231                                 appendConstraint(constraint, buffer);
232                             }
233                         }
234                         if (buffer.length() > 0) {
235                             definition.setAnnotation(buffer.toString());
236                         }
237                         classGenerator.addMethod(definition);
238 
239                     }
240                 }
241                 if (importedTypes.size() > 0) {
242                     for (String className : importedTypes.values()) {
243                         imports.add(className);
244                     }
245                 }
246                 if (anyRequired) {
247                     imports.add(REQUIRED_IMPORT);
248                 }
249                 boolean firstReqCtx = true;
250                 if (requestContextAttrs.size() > 0) {
251                     imports.add(REQUEST_CONTEXT_IMPORT);
252                     StringBuilder reqCtx = new StringBuilder();
253                     for (Map.Entry<String, Attribute> entry : requestContextAttrs.entrySet()) {
254                         if (!firstReqCtx) {
255                             reqCtx.append(")\n");
256                         }
257                         firstReqCtx = false;
258                         reqCtx.append(REQCTX_ANN);
259                         reqCtx.append(KEY).append(entry.getKey()).append("\"");
260                         Attribute attrib = entry.getValue();
261                         String name = attrib.getName();
262                         if (name.startsWith(REQCTX)) {
263                             name = name.substring(REQCTX.length());
264                         }
265                         Boolean isRequired = null;
266                         final String attrName = name;
267                         if (event.getAttributes() != null) {
268                             Optional<EventAttribute> optional = event.getAttributes().stream().filter(a -> attrName.equals(a.getName())).findFirst();
269                             if (optional.isPresent()) {
270                                 isRequired = optional.get().isRequired();
271                             }
272                         }
273                         if ((isRequired != null && isRequired) ||
274                                 (isRequired == null && requestContextIsRequired.get(name))) {
275                             reqCtx.append(", ").append(REQUIRED_ATTR);
276                         }
277                         Set<Constraint> constraints =  entry.getValue().getConstraints();
278                         if (constraints != null && constraints.size() > 0) {
279                             anyConstraints = true;
280                             reqCtx.append(CONSTRAINTS_ATTR);
281                             boolean first = true;
282                             for (Constraint constraint : constraints) {
283                                 if (!first) {
284                                     reqCtx.append(", ");
285                                 }
286                                 first = false;
287                                 appendConstraint(constraint, reqCtx);
288                             }
289                             reqCtx.append("}");
290 
291                         }
292                     }
293                     reqCtx.append(")");
294                     if (annotations.length() > 0) {
295                         annotations.append("\n");
296                     }
297                     annotations.append(reqCtx.toString());
298                 }
299                 if (anyConstraints) {
300                     imports.add(CONSTRAINT_IMPORT);
301                 }
302                 if (annotations.length() > 0) {
303                     classGenerator.setAnnotations(annotations.toString());
304                 }
305                 classGenerator.generate();
306             }
307         }
308         if (errors) {
309             throw new IllegalStateException("Errors were encountered during code generation");
310         }
311     }
312 
313     void appendConstraint(Constraint constraint, StringBuilder buffer) {
314         ConstraintType type = constraint.getConstraintType();
315         // Add the escapes since they have been removed when converting the original data to a Java Strinng. They need to
316         // be added back for use in the Constraint declaration.
317         buffer.append(String.format(CONSTRAINT, type.getName(), constraint.getValue().replace("\\", "\\\\")));
318     }
319 }