1 /*
   2  * Copyright (c) 2016, 2019, 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  * {
  56  *     @code
  57  *     List<ValueDescriptor> fields = new ArrayList<>();
  58  *     List<AnnotationElement> messageAnnotations = Collections.singletonList(new AnnotationElement(Label.class, "Message"));
  59  *     fields.add(new ValueDescriptor(String.class, "message", messageAnnotations));
  60  *     List<AnnotationElement> numberAnnotations = Collections.singletonList(new AnnotationElement(Label.class, "Number"));
  61  *     fields.add(new ValueDescriptor(int.class, "number", numberAnnotations));
  62  *
  63  *     String[] category = { "Example", "Getting Started" };
  64  *     List<AnnotationElement> eventAnnotations = new ArrayList<>();
  65  *     eventAnnotations.add(new AnnotationElement(Name.class, "com.example.HelloWorld"));
  66  *     eventAnnotations.add(new AnnotationElement(Label.class, "Hello World"));
  67  *     eventAnnotations.add(new AnnotationElement(Description.class, "Helps programmer getting started"));
  68  *     eventAnnotations.add(new AnnotationElement(Category.class, category));
  69  *
  70  *     EventFactory f = EventFactory.create(eventAnnotations, fields);
  71  *
  72  *     Event event = f.newEvent();
  73  *     event.set(0, "hello, world!");
  74  *     event.set(1, 4711);
  75  *     event.commit();
  76  * }
  77  * </pre>
  78  *
  79  * @since 9
  80  */
  81 public final class EventFactory {
  82 
  83     private static final long REGSITERED_ID = Type.getTypeId(Registered.class);
  84 
  85     private final Class<? extends Event> eventClass;
  86     private final MethodHandle constructorHandle;
  87     private final List<AnnotationElement> sanitizedAnnotation;
  88     private final List<ValueDescriptor> sanitizedFields;
  89 
  90     private EventFactory(Class<? extends Event> eventClass, List<AnnotationElement> sanitizedAnnotation, List<ValueDescriptor> sanitizedFields) throws IllegalAccessException, NoSuchMethodException, SecurityException {
  91         this.constructorHandle = MethodHandles.lookup().unreflectConstructor(eventClass.getConstructor());
  92         this.eventClass = eventClass;
  93         this.sanitizedAnnotation = sanitizedAnnotation;
  94         this.sanitizedFields = sanitizedFields;
  95     }
  96 
  97     /**
  98      * Creates an {@code EventFactory} object.
  99      * <p>
 100      * The order of the value descriptors specifies the index to use when setting
 101      * event values.
 102      *
 103      * @param annotationElements list of annotation elements that describes the
 104      *        annotations on the event, not {@code null}
 105      *
 106      * @param fields list of descriptors that describes the fields of the event, not
 107      *        {@code null}
 108      *
 109      * @return event factory, not {@code null}
 110      *
 111      * @throws IllegalArgumentException if the input is not valid. For example,
 112      *         input might not be valid if the field type or name is not valid in
 113      *         the Java language or an annotation element references a type that
 114      *         can't be found.
 115      *
 116      * @throws SecurityException if a security manager exists and the caller does
 117      *         not have {@code FlightRecorderPermission("registerEvent")}
 118      *
 119      * @see Event#set(int, Object)
 120      */
 121     public static EventFactory create(List<AnnotationElement> annotationElements, List<ValueDescriptor> fields) {
 122         Objects.requireNonNull(fields);
 123         Objects.requireNonNull(annotationElements);
 124         JVMSupport.ensureWithInternalError();
 125 
 126         Utils.checkRegisterPermission();
 127 
 128         List<AnnotationElement> sanitizedAnnotation = Utils.sanitizeNullFreeList(annotationElements, AnnotationElement.class);
 129         List<ValueDescriptor> sanitizedFields = Utils.sanitizeNullFreeList(fields, ValueDescriptor.class);
 130         Set<String> nameSet = new HashSet<>();
 131         for (ValueDescriptor v : sanitizedFields) {
 132             String name = v.getName();
 133             if (v.isArray()) {
 134                 throw new IllegalArgumentException("Array types are not allowed for fields");
 135             }
 136             if (!Type.isValidJavaFieldType(v.getTypeName())) {
 137                 throw new IllegalArgumentException(v.getTypeName() + " is not a valid type for an event field");
 138             }
 139             if (!Type.isValidJavaIdentifier(v.getName())) {
 140                 throw new IllegalArgumentException(name + " is not a valid name for an event field");
 141             }
 142             if (nameSet.contains(name)) {
 143                 throw new IllegalArgumentException("Name of fields must be unique. Found two instances of " + name);
 144             }
 145             nameSet.add(name);
 146         }
 147 
 148         // Prevent event from being registered in <clinit>
 149         // and only use annotations that can be resolved (those in boot class loader)
 150         boolean needRegister = true;
 151         List<AnnotationElement> bootAnnotations = new ArrayList<>();
 152         for (AnnotationElement ae : sanitizedAnnotation) {
 153             long id = ae.getTypeId();
 154             if (ae.isInBoot()) {
 155                 if (id == REGSITERED_ID) {
 156                     if (Boolean.FALSE.equals(ae.getValue("value"))) {
 157                         needRegister = false;
 158                     }
 159                 } else {
 160                     bootAnnotations.add(ae);
 161                 }
 162             }
 163         }
 164         bootAnnotations.add(new AnnotationElement(Registered.class, false));
 165 
 166         EventClassBuilder ecb = new EventClassBuilder(bootAnnotations, sanitizedFields);
 167         Class<? extends Event> eventClass = ecb.build();
 168 
 169         if (needRegister) {
 170             MetadataRepository.getInstance().register(eventClass, sanitizedAnnotation, sanitizedFields);
 171         }
 172         try {
 173             return new EventFactory(eventClass, sanitizedAnnotation, sanitizedFields);
 174         } catch (IllegalAccessException e) {
 175             throw new IllegalAccessError("Could not accees constructor of generated event handler, " + e.getMessage());
 176         } catch (NoSuchMethodException e) {
 177             throw new InternalError("Could not find constructor in generated event handler, " + e.getMessage());
 178         }
 179     }
 180 
 181     /**
 182      * Instantiates an event, so it can be populated with data and written to the
 183      * Flight Recorder system.
 184      * <p>
 185      * Use the {@link Event#set(int, Object)} method to set a value.
 186      *
 187      * @return an event instance, not {@code null}
 188      */
 189     public Event newEvent() {
 190         try {
 191             return (Event) constructorHandle.invoke();
 192         } catch (Throwable e) {
 193             throw new InstantiationError("Could not instantaite dynamically generated event class " + eventClass.getName() + ". " + e.getMessage());
 194         }
 195     }
 196 
 197     /**
 198      * Returns the event type that is associated with this event factory.
 199      *
 200      * @return event type that is associated with this event factory, not
 201      *         {@code null}
 202      *
 203      * @throws java.lang.IllegalStateException if the event factory is created with
 204      *         the {@code Registered(false)} annotation and the event class is not
 205      *         manually registered before the invocation of this method
 206      */
 207     public EventType getEventType() {
 208         return EventType.getEventType(eventClass);
 209     }
 210 
 211     /**
 212      * Registers an unregistered event.
 213      * <p>
 214      * By default, the event class associated with this event factory is registered
 215      * when the event factory is created, unless the event has the
 216      * {@link Registered} annotation.
 217      * <p>
 218      * A registered event class can write data to Flight Recorder and event metadata
 219      * can be obtained by invoking {@link FlightRecorder#getEventTypes()}.
 220      * <p>
 221      * If the event class associated with this event factory is already registered,
 222      * the call to this method is ignored.
 223      *
 224      * @throws SecurityException if a security manager exists and the caller
 225      *         does not have {@code FlightRecorderPermission("registerEvent")}
 226      * @see Registered
 227      * @see FlightRecorder#register(Class)
 228      */
 229     public void register() {
 230         MetadataRepository.getInstance().register(eventClass, sanitizedAnnotation, sanitizedFields);
 231     }
 232 
 233     /**
 234      * Unregisters the event that is associated with this event factory.
 235      * <p>
 236      * A unregistered event class can't write data to Flight Recorder and event
 237      * metadata can't be obtained by invoking
 238      * {@link FlightRecorder#getEventTypes()}.
 239      * <p>
 240      * If the event class associated with this event factory is not already
 241      * registered, the call to this method is ignored.
 242      *
 243      * @throws SecurityException if a security manager exists and the caller does
 244      *         not have {@code FlightRecorderPermission("registerEvent")}
 245      * @see Registered
 246      * @see FlightRecorder#unregister(Class)
 247      */
 248     public void unregister() {
 249         MetadataRepository.getInstance().unregister(eventClass);
 250     }
 251 
 252 }