/
EventFactory.java
252 lines (234 loc) · 10.5 KB
/
EventFactory.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
/*
* Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.jfr;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import jdk.internal.module.Checks;
import jdk.jfr.internal.EventClassBuilder;
import jdk.jfr.internal.JVMSupport;
import jdk.jfr.internal.MetadataRepository;
import jdk.jfr.internal.Type;
import jdk.jfr.internal.Utils;
/**
* Class for defining an event at runtime.
* <p>
* It's highly recommended that the event is defined at compile time, if the
* field layout is known, so the Java Virtual Machine (JVM) can optimize the
* code, possibly remove all instrumentation if Flight Recorder is inactive or
* if the enabled setting for this event is set to {@code false}.
* <p>
* To define an event at compile time, see {@link Event}.
* <p>
* The following example shows how to implement a dynamic {@code Event} class.
*
* <pre>
* {@code
* List<ValueDescriptor> fields = new ArrayList<>();
* List<AnnotationElement> messageAnnotations = Collections.singletonList(new AnnotationElement(Label.class, "Message"));
* fields.add(new ValueDescriptor(String.class, "message", messageAnnotations));
* List<AnnotationElement> numberAnnotations = Collections.singletonList(new AnnotationElement(Label.class, "Number"));
* fields.add(new ValueDescriptor(int.class, "number", numberAnnotations));
*
* String[] category = { "Example", "Getting Started" };
* List<AnnotationElement> eventAnnotations = new ArrayList<>();
* eventAnnotations.add(new AnnotationElement(Name.class, "com.example.HelloWorld"));
* eventAnnotations.add(new AnnotationElement(Label.class, "Hello World"));
* eventAnnotations.add(new AnnotationElement(Description.class, "Helps programmer getting started"));
* eventAnnotations.add(new AnnotationElement(Category.class, category));
*
* EventFactory f = EventFactory.create(eventAnnotations, fields);
*
* Event event = f.newEvent();
* event.set(0, "hello, world!");
* event.set(1, 4711);
* event.commit();
* }
* </pre>
*
* @since 9
*/
public final class EventFactory {
private static final long REGISTERED_ID = Type.getTypeId(Registered.class);
private final Class<? extends Event> eventClass;
private final MethodHandle constructorHandle;
private final List<AnnotationElement> sanitizedAnnotation;
private final List<ValueDescriptor> sanitizedFields;
private EventFactory(Class<? extends Event> eventClass, List<AnnotationElement> sanitizedAnnotation, List<ValueDescriptor> sanitizedFields) throws IllegalAccessException, NoSuchMethodException, SecurityException {
this.constructorHandle = MethodHandles.lookup().unreflectConstructor(eventClass.getConstructor());
this.eventClass = eventClass;
this.sanitizedAnnotation = sanitizedAnnotation;
this.sanitizedFields = sanitizedFields;
}
/**
* Creates an {@code EventFactory} object.
* <p>
* The order of the value descriptors specifies the index to use when setting
* event values.
*
* @param annotationElements list of annotation elements that describes the
* annotations on the event, not {@code null}
*
* @param fields list of descriptors that describes the fields of the event, not
* {@code null}
*
* @return event factory, not {@code null}
*
* @throws IllegalArgumentException if the input is not valid. For example,
* input might not be valid if the field type or name is not valid in
* the Java language or an annotation element references a type that
* can't be found.
*
* @throws SecurityException if a security manager exists and the caller does
* not have {@code FlightRecorderPermission("registerEvent")}
*
* @see Event#set(int, Object)
*/
public static EventFactory create(List<AnnotationElement> annotationElements, List<ValueDescriptor> fields) {
Objects.requireNonNull(fields);
Objects.requireNonNull(annotationElements);
JVMSupport.ensureWithInternalError();
Utils.checkRegisterPermission();
List<AnnotationElement> sanitizedAnnotation = Utils.sanitizeNullFreeList(annotationElements, AnnotationElement.class);
List<ValueDescriptor> sanitizedFields = Utils.sanitizeNullFreeList(fields, ValueDescriptor.class);
Set<String> nameSet = new HashSet<>();
for (ValueDescriptor v : sanitizedFields) {
String name = v.getName();
if (v.isArray()) {
throw new IllegalArgumentException("Array types are not allowed for fields");
}
if (!Type.isValidJavaFieldType(v.getTypeName())) {
throw new IllegalArgumentException(v.getTypeName() + " is not a valid type for an event field");
}
if (!Checks.isJavaIdentifier(v.getName())) {
throw new IllegalArgumentException(name + " is not a valid name for an event field");
}
if (nameSet.contains(name)) {
throw new IllegalArgumentException("Name of fields must be unique. Found two instances of " + name);
}
nameSet.add(name);
}
// Prevent event from being registered in <clinit>
// and only use annotations that can be resolved (those in boot class loader)
boolean needRegister = true;
List<AnnotationElement> bootAnnotations = new ArrayList<>();
for (AnnotationElement ae : sanitizedAnnotation) {
long id = ae.getTypeId();
if (ae.isInBoot()) {
if (id == REGISTERED_ID) {
if (Boolean.FALSE.equals(ae.getValue("value"))) {
needRegister = false;
}
} else {
bootAnnotations.add(ae);
}
}
}
bootAnnotations.add(new AnnotationElement(Registered.class, false));
EventClassBuilder ecb = new EventClassBuilder(bootAnnotations, sanitizedFields);
Class<? extends Event> eventClass = ecb.build();
if (needRegister) {
MetadataRepository.getInstance().register(eventClass, sanitizedAnnotation, sanitizedFields);
}
try {
return new EventFactory(eventClass, sanitizedAnnotation, sanitizedFields);
} catch (IllegalAccessException e) {
throw new IllegalAccessError("Could not access constructor of generated event handler, " + e.getMessage());
} catch (NoSuchMethodException e) {
throw new InternalError("Could not find constructor in generated event handler, " + e.getMessage());
}
}
/**
* Instantiates an event, so it can be populated with data and written to the
* Flight Recorder system.
* <p>
* Use the {@link Event#set(int, Object)} method to set a value.
*
* @return an event instance, not {@code null}
*/
public Event newEvent() {
try {
return (Event) constructorHandle.invoke();
} catch (Throwable e) {
throw new InstantiationError("Could not instantiate dynamically generated event class " + eventClass.getName() + ". " + e.getMessage());
}
}
/**
* Returns the event type that is associated with this event factory.
*
* @return event type that is associated with this event factory, not
* {@code null}
*
* @throws java.lang.IllegalStateException if the event factory is created with
* the {@code Registered(false)} annotation and the event class is not
* manually registered before the invocation of this method
*/
public EventType getEventType() {
return EventType.getEventType(eventClass);
}
/**
* Registers an unregistered event.
* <p>
* By default, the event class associated with this event factory is registered
* when the event factory is created, unless the event has the
* {@link Registered} annotation.
* <p>
* A registered event class can write data to Flight Recorder and event metadata
* can be obtained by invoking {@link FlightRecorder#getEventTypes()}.
* <p>
* If the event class associated with this event factory is already registered,
* the call to this method is ignored.
*
* @throws SecurityException if a security manager exists and the caller
* does not have {@code FlightRecorderPermission("registerEvent")}
* @see Registered
* @see FlightRecorder#register(Class)
*/
public void register() {
MetadataRepository.getInstance().register(eventClass, sanitizedAnnotation, sanitizedFields);
}
/**
* Unregisters the event that is associated with this event factory.
* <p>
* A unregistered event class can't write data to Flight Recorder and event
* metadata can't be obtained by invoking
* {@link FlightRecorder#getEventTypes()}.
* <p>
* If the event class associated with this event factory is not already
* registered, the call to this method is ignored.
*
* @throws SecurityException if a security manager exists and the caller does
* not have {@code FlightRecorderPermission("registerEvent")}
* @see Registered
* @see FlightRecorder#unregister(Class)
*/
public void unregister() {
MetadataRepository.getInstance().unregister(eventClass);
}
}