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