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.core.appender.db.jpa; 18 19 import java.util.Map; 20 21 import javax.persistence.Inheritance; 22 import javax.persistence.InheritanceType; 23 import javax.persistence.MappedSuperclass; 24 import javax.persistence.Transient; 25 26 import org.apache.logging.log4j.Level; 27 import org.apache.logging.log4j.Marker; 28 import org.apache.logging.log4j.ThreadContext; 29 import org.apache.logging.log4j.core.LogEvent; 30 import org.apache.logging.log4j.message.Message; 31 32 /** 33 * <p> 34 * Users of the JPA appender MUST extend this class, using JPA annotations on the concrete class and all of its 35 * accessor methods (as needed) to map them to the proper table and columns. Accessors you do not want persisted should 36 * be annotated with {@link Transient @Transient}. All accessors should call {@link #getWrappedEvent()} and delegate the 37 * call to the underlying event. Users may want to instead extend {@link BasicLogEventEntity}, which takes care of all 38 * of this for you. 39 * </p> 40 * <p> 41 * The concrete class must have two constructors: a public no-arg constructor to convince the JPA provider that it's a 42 * valid entity, and a public constructor that takes a single {@link LogEvent event} and passes it to the parent class 43 * with {@link #AbstractLogEventWrapperEntity(LogEvent) super(event)}. Furthermore, the concrete class must be annotated 44 * {@link javax.persistence.Entity @Entity} and {@link javax.persistence.Table @Table} and must implement a fully 45 * mutable ID property annotated with {@link javax.persistence.Id @Id} and 46 * {@link javax.persistence.GeneratedValue @GeneratedValue} to tell the JPA provider how to calculate an ID for new 47 * events. 48 * </p> 49 * <p> 50 * Many of the return types of {@link LogEvent} methods (e.g., {@link StackTraceElement}, {@link Message}, 51 * {@link Marker}, {@link Throwable}, 52 * {@link org.apache.logging.log4j.ThreadContext.ContextStack ThreadContext.ContextStack}, and 53 * {@link Map Map<String, String>}) will not be recognized by the JPA provider. In conjunction with 54 * {@link javax.persistence.Convert @Convert}, you can use the converters in the 55 * {@link org.apache.logging.log4j.core.appender.db.jpa.converter} package to convert these types to database columns. 56 * If you want to retrieve log events from the database, you can create a true POJO entity and also use these 57 * converters for extracting persisted values.<br> 58 * </p> 59 * <p> 60 * The mutator methods in this class not specified in {@link LogEvent} are no-op methods, implemented to satisfy the JPA 61 * requirement that accessor methods have matching mutator methods. If you create additional accessor methods, you must 62 * likewise create matching no-op mutator methods. 63 * </p> 64 * 65 * @see BasicLogEventEntity 66 */ 67 @MappedSuperclass 68 @Inheritance(strategy = InheritanceType.SINGLE_TABLE) 69 public abstract class AbstractLogEventWrapperEntity implements LogEvent { 70 private static final long serialVersionUID = 1L; 71 72 private final LogEvent wrappedEvent; 73 74 /** 75 * Instantiates this base class. All concrete implementations must have a constructor matching this constructor's 76 * signature. The no-argument constructor is required for a standards-compliant JPA provider to accept this as an 77 * entity. 78 */ 79 @SuppressWarnings("unused") 80 protected AbstractLogEventWrapperEntity() { 81 this(new NullLogEvent()); 82 } 83 84 /** 85 * Instantiates this base class. All concrete implementations must have a constructor matching this constructor's 86 * signature. This constructor is used for wrapping this entity around a logged event. 87 * 88 * @param wrappedEvent The underlying event from which information is obtained. 89 */ 90 protected AbstractLogEventWrapperEntity(final LogEvent wrappedEvent) { 91 if (wrappedEvent == null) { 92 throw new IllegalArgumentException("The wrapped event cannot be null."); 93 } 94 this.wrappedEvent = wrappedEvent; 95 } 96 97 /** 98 * All eventual accessor methods must call this method and delegate the method call to the underlying wrapped event. 99 * Annotated {@link Transient} so as not to be included in the persisted entity. 100 * 101 * @return The underlying event from which information is obtained. 102 */ 103 @Transient 104 protected final LogEvent getWrappedEvent() { 105 return this.wrappedEvent; 106 } 107 108 /** 109 * A no-op mutator to satisfy JPA requirements, as this entity is write-only. 110 * 111 * @param level Ignored. 112 */ 113 @SuppressWarnings("unused") 114 public void setLevel(final Level level) { 115 // this entity is write-only 116 } 117 118 /** 119 * A no-op mutator to satisfy JPA requirements, as this entity is write-only. 120 * 121 * @param loggerName Ignored. 122 */ 123 @SuppressWarnings("unused") 124 public void setLoggerName(final String loggerName) { 125 // this entity is write-only 126 } 127 128 /** 129 * A no-op mutator to satisfy JPA requirements, as this entity is write-only. 130 * 131 * @param source Ignored. 132 */ 133 @SuppressWarnings("unused") 134 public void setSource(final StackTraceElement source) { 135 // this entity is write-only 136 } 137 138 /** 139 * A no-op mutator to satisfy JPA requirements, as this entity is write-only. 140 * 141 * @param message Ignored. 142 */ 143 @SuppressWarnings("unused") 144 public void setMessage(final Message message) { 145 // this entity is write-only 146 } 147 148 /** 149 * A no-op mutator to satisfy JPA requirements, as this entity is write-only. 150 * 151 * @param marker Ignored. 152 */ 153 @SuppressWarnings("unused") 154 public void setMarker(final Marker marker) { 155 // this entity is write-only 156 } 157 158 /** 159 * A no-op mutator to satisfy JPA requirements, as this entity is write-only. 160 * 161 * @param threadName Ignored. 162 */ 163 @SuppressWarnings("unused") 164 public void setThreadName(final String threadName) { 165 // this entity is write-only 166 } 167 168 /** 169 * A no-op mutator to satisfy JPA requirements, as this entity is write-only. 170 * 171 * @param millis Ignored. 172 */ 173 @SuppressWarnings("unused") 174 public void setMillis(final long millis) { 175 // this entity is write-only 176 } 177 178 /** 179 * A no-op mutator to satisfy JPA requirements, as this entity is write-only. 180 * 181 * @param throwable Ignored. 182 */ 183 @SuppressWarnings("unused") 184 public void setThrown(final Throwable throwable) { 185 // this entity is write-only 186 } 187 188 /** 189 * A no-op mutator to satisfy JPA requirements, as this entity is write-only. 190 * 191 * @param contextMap Ignored. 192 */ 193 @SuppressWarnings("unused") 194 public void setContextMap(final Map<String, String> contextMap) { 195 // this entity is write-only 196 } 197 198 /** 199 * A no-op mutator to satisfy JPA requirements, as this entity is write-only. 200 * 201 * @param contextStack Ignored. 202 */ 203 @SuppressWarnings("unused") 204 public void setContextStack(final ThreadContext.ContextStack contextStack) { 205 // this entity is write-only 206 } 207 208 /** 209 * A no-op mutator to satisfy JPA requirements, as this entity is write-only. 210 * 211 * @param fqcn Ignored. 212 */ 213 @SuppressWarnings("unused") 214 public void setFQCN(final String fqcn) { 215 // this entity is write-only 216 } 217 218 /** 219 * Indicates whether the source of the logging request is required downstream. Annotated 220 * {@link Transient @Transient} so as to not be included in the persisted entity. 221 * 222 * @return whether the source of the logging request is required downstream. 223 */ 224 @Override 225 @Transient 226 public final boolean isIncludeLocation() { 227 return this.getWrappedEvent().isIncludeLocation(); 228 } 229 230 @Override 231 public final void setIncludeLocation(final boolean locationRequired) { 232 this.getWrappedEvent().setIncludeLocation(locationRequired); 233 } 234 235 /** 236 * Indicates whether this event is the last one in a batch. Annotated {@link Transient @Transient} so as to not be 237 * included in the persisted entity. 238 * 239 * @return whether this event is the last one in a batch. 240 */ 241 @Override 242 @Transient 243 public final boolean isEndOfBatch() { 244 return this.getWrappedEvent().isEndOfBatch(); 245 } 246 247 @Override 248 public final void setEndOfBatch(final boolean endOfBatch) { 249 this.getWrappedEvent().setEndOfBatch(endOfBatch); 250 } 251 252 /** 253 * A no-op log event class to prevent {@code NullPointerException}s. O/RMs tend to create instances of entities in 254 * order to "play around" with them. 255 */ 256 private static class NullLogEvent implements LogEvent { 257 private static final long serialVersionUID = 1L; 258 259 @Override 260 public Level getLevel() { 261 return null; 262 } 263 264 @Override 265 public String getLoggerName() { 266 return null; 267 } 268 269 @Override 270 public StackTraceElement getSource() { 271 return null; 272 } 273 274 @Override 275 public Message getMessage() { 276 return null; 277 } 278 279 @Override 280 public Marker getMarker() { 281 return null; 282 } 283 284 @Override 285 public String getThreadName() { 286 return null; 287 } 288 289 @Override 290 public long getMillis() { 291 return 0; 292 } 293 294 @Override 295 public Throwable getThrown() { 296 return null; 297 } 298 299 @Override 300 public Map<String, String> getContextMap() { 301 return null; 302 } 303 304 @Override 305 public ThreadContext.ContextStack getContextStack() { 306 return null; 307 } 308 309 @Override 310 public String getFQCN() { 311 return null; 312 } 313 314 @Override 315 public boolean isIncludeLocation() { 316 return false; 317 } 318 319 @Override 320 public void setIncludeLocation(final boolean locationRequired) { 321 322 } 323 324 @Override 325 public boolean isEndOfBatch() { 326 return false; 327 } 328 329 @Override 330 public void setEndOfBatch(final boolean endOfBatch) { 331 332 } 333 } 334 }