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 
  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      */
 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      */
 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      */
 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      */
 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      */
 247     public void unregister() {
 248         MetadataRepository.getInstance().unregister(eventClass);
 249     }
 250 
 251 }