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