[ 
https://issues.apache.org/jira/browse/NIFI-1976?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=15353228#comment-15353228
 ] 

ASF GitHub Bot commented on NIFI-1976:
--------------------------------------

Github user JPercivall commented on a diff in the pull request:

    https://github.com/apache/nifi/pull/525#discussion_r68780468
  
    --- Diff: 
nifi-nar-bundles/nifi-windows-event-log-bundle/nifi-windows-event-log-processors/src/main/java/org/apache/nifi/processors/windows/event/log/ConsumeWindowsEventLog.java
 ---
    @@ -0,0 +1,282 @@
    +/*
    + * Licensed to the Apache Software Foundation (ASF) under one or more
    + * contributor license agreements.  See the NOTICE file distributed with
    + * this work for additional information regarding copyright ownership.
    + * The ASF licenses this file to You under the Apache License, Version 2.0
    + * (the "License"); you may not use this file except in compliance with
    + * the License.  You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +package org.apache.nifi.processors.windows.event.log;
    +
    +import com.sun.jna.platform.win32.Kernel32;
    +import com.sun.jna.platform.win32.Kernel32Util;
    +import com.sun.jna.platform.win32.WinNT;
    +import org.apache.commons.io.Charsets;
    +import org.apache.nifi.annotation.behavior.InputRequirement;
    +import org.apache.nifi.annotation.behavior.TriggerSerially;
    +import org.apache.nifi.annotation.behavior.WritesAttribute;
    +import org.apache.nifi.annotation.behavior.WritesAttributes;
    +import org.apache.nifi.annotation.documentation.CapabilityDescription;
    +import org.apache.nifi.annotation.documentation.Tags;
    +import org.apache.nifi.annotation.lifecycle.OnScheduled;
    +import org.apache.nifi.annotation.lifecycle.OnStopped;
    +import org.apache.nifi.components.PropertyDescriptor;
    +import org.apache.nifi.components.ValidationContext;
    +import org.apache.nifi.components.ValidationResult;
    +import org.apache.nifi.flowfile.FlowFile;
    +import org.apache.nifi.flowfile.attributes.CoreAttributes;
    +import org.apache.nifi.processor.AbstractSessionFactoryProcessor;
    +import org.apache.nifi.processor.ProcessContext;
    +import org.apache.nifi.processor.ProcessSession;
    +import org.apache.nifi.processor.ProcessSessionFactory;
    +import org.apache.nifi.processor.Relationship;
    +import org.apache.nifi.processor.exception.ProcessException;
    +import org.apache.nifi.processor.util.StandardValidators;
    +import org.apache.nifi.processors.windows.event.log.jna.ErrorLookup;
    +import 
org.apache.nifi.processors.windows.event.log.jna.EventSubscribeXmlRenderingCallback;
    +import org.apache.nifi.processors.windows.event.log.jna.WEvtApi;
    +
    +import java.net.URI;
    +import java.util.ArrayList;
    +import java.util.Arrays;
    +import java.util.Collection;
    +import java.util.Collections;
    +import java.util.HashSet;
    +import java.util.List;
    +import java.util.Set;
    +import java.util.concurrent.BlockingQueue;
    +import java.util.concurrent.LinkedBlockingQueue;
    +
    +
    +@InputRequirement(InputRequirement.Requirement.INPUT_FORBIDDEN)
    +@Tags({"ingest", "event", "windows"})
    +@TriggerSerially
    +@CapabilityDescription("Registers a Windows Event Log Subscribe Callback 
to receive FlowFiles from Events on Windows.  These can be filtered via channel 
and XPath.")
    +@WritesAttributes({
    +        @WritesAttribute(attribute = "mime.type", description = "Will set 
a MIME type value of application/xml.")
    +})
    +public class ConsumeWindowsEventLog extends 
AbstractSessionFactoryProcessor {
    +    public static final String DEFAULT_CHANNEL = "System";
    +    public static final String DEFAULT_XPATH = "*";
    +    public static final int DEFAULT_MAX_BUFFER = 1024 * 1024;
    +    public static final int DEFAULT_MAX_QUEUE_SIZE = 1024;
    +
    +    public static final PropertyDescriptor CHANNEL = new 
PropertyDescriptor.Builder()
    +            .name("channel")
    +            .displayName("Channel")
    +            .required(true)
    +            .defaultValue(DEFAULT_CHANNEL)
    +            .description("The Windows Event Log Channel to listen to.")
    +            .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
    +            .build();
    +
    +    public static final PropertyDescriptor QUERY = new 
PropertyDescriptor.Builder()
    +            .name("query")
    +            .displayName("XPath Query")
    +            .required(true)
    +            .defaultValue(DEFAULT_XPATH)
    +            .description("XPath Query to filter events. (See 
https://msdn.microsoft.com/en-us/library/windows/desktop/dd996910(v=vs.85).aspx 
for examples.)")
    +            .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
    +            .build();
    +
    +    public static final PropertyDescriptor MAX_BUFFER_SIZE = new 
PropertyDescriptor.Builder()
    +            .name("maxBuffer")
    +            .displayName("Maximum Buffer Size")
    +            .required(true)
    +            .defaultValue(Integer.toString(DEFAULT_MAX_BUFFER))
    +            .description("The individual Event Log XMLs are rendered to a 
buffer." +
    +                    "  This specifies the maximum size in bytes that the 
buffer will be allowed to grow to. (Limiting the maximum size of an individual 
Event XML.)")
    +            .addValidator(StandardValidators.POSITIVE_INTEGER_VALIDATOR)
    +            .build();
    +
    +    public static final PropertyDescriptor MAX_EVENT_QUEUE_SIZE = new 
PropertyDescriptor.Builder()
    +            .name("maxQueue")
    +            .displayName("Maximum queue size")
    +            .required(true)
    +            .defaultValue(Integer.toString(DEFAULT_MAX_QUEUE_SIZE))
    +            .description("Maximum number of events to queue for 
transformation into FlowFiles.")
    +            .addValidator(StandardValidators.POSITIVE_INTEGER_VALIDATOR)
    +            .build();
    +
    +    public static final List<PropertyDescriptor> PROPERTY_DESCRIPTORS = 
Collections.unmodifiableList(Arrays.asList(CHANNEL, QUERY, MAX_BUFFER_SIZE, 
MAX_EVENT_QUEUE_SIZE));
    +
    +    public static final Relationship REL_SUCCESS = new 
Relationship.Builder()
    +            .name("success")
    +            .description("Relationship for successfully consumed events.")
    +            .build();
    +
    +    public static final Set<Relationship> RELATIONSHIPS = 
Collections.unmodifiableSet(new HashSet<>(Arrays.asList(REL_SUCCESS)));
    +    public static final String APPLICATION_XML = "application/xml";
    +    private final WEvtApi wEvtApi;
    +    private final Kernel32 kernel32;
    +    private final ErrorLookup errorLookup;
    +    private final String name;
    +
    +    private Throwable wEvtApiError = null;
    +    private Throwable kernel32Error = null;
    +
    +    private BlockingQueue<String> renderedXMLs;
    +    private WEvtApi.EVT_SUBSCRIBE_CALLBACK evtSubscribeCallback;
    +    private WinNT.HANDLE subscriptionHandle;
    +    private ProcessSessionFactory sessionFactory;
    +    private String provenanceUri;
    +
    +    /**
    +     * Framework constructor
    +     */
    +    public ConsumeWindowsEventLog() {
    +        this(null, null);
    +    }
    +
    +    /**
    +     * Constructor that allows injection of JNA interfaces
    +     *
    +     * @param wEvtApi  event api interface
    +     * @param kernel32 kernel interface
    +     */
    +    public ConsumeWindowsEventLog(WEvtApi wEvtApi, Kernel32 kernel32) {
    +        this.wEvtApi = wEvtApi == null ? loadWEvtApi() : wEvtApi;
    +        this.kernel32 = kernel32 == null ? loadKernel32() : kernel32;
    +        this.errorLookup = new ErrorLookup(this.kernel32);
    +        if (kernel32 != null) {
    +            name = Kernel32Util.getComputerName();
    +        } else {
    +            // Won't be able to use the processor anyway because native 
libraries didn't load
    +            name = null;
    +        }
    +    }
    +
    +    private WEvtApi loadWEvtApi() {
    +        try {
    +            return WEvtApi.INSTANCE;
    +        } catch (Throwable e) {
    +            wEvtApiError = e;
    +            return null;
    +        }
    +    }
    +
    +    private Kernel32 loadKernel32() {
    +        try {
    +            return Kernel32.INSTANCE;
    +        } catch (Throwable e) {
    +            kernel32Error = e;
    +            return null;
    +        }
    +    }
    +
    +    /**
    +     * Register subscriber via native call
    +     *
    +     * @param context the process context
    +     */
    +    @OnScheduled
    +    public void onScheduled(ProcessContext context) throws Exception {
    +        String channel = context.getProperty(CHANNEL).getValue();
    +        String query = context.getProperty(QUERY).getValue();
    +
    +        renderedXMLs = new 
LinkedBlockingQueue<>(context.getProperty(MAX_EVENT_QUEUE_SIZE).asInteger());
    +        provenanceUri = new URI("winlog", name, "/" + channel, query, 
null).toASCIIString();
    +
    +        evtSubscribeCallback = new 
EventSubscribeXmlRenderingCallback(getLogger(), s -> {
    +            try {
    +                renderedXMLs.put(s);
    +            } catch (InterruptedException e) {
    +                throw new IllegalStateException("Got interrupted while 
waiting to add to queue.", e);
    +            }
    +        }, context.getProperty(MAX_BUFFER_SIZE).asInteger(), wEvtApi, 
kernel32, errorLookup);
    +
    +        subscriptionHandle = wEvtApi.EvtSubscribe(null, null, channel, 
query, null, null,
    +                evtSubscribeCallback, 
WEvtApi.EvtSubscribeFlags.SUBSCRIBE_TO_FUTURE);
    +        if (subscriptionHandle == null || subscriptionHandle.getPointer() 
== null) {
    +            throw new Exception("Unable to subscribe with provided 
parameters, received the following error code: "
    +                    + errorLookup.getLastError());
    +        }
    +    }
    +
    +    /**
    +     * Cleanup
    +     */
    +    @OnStopped
    +    public void stop() {
    +        wEvtApi.EvtClose(subscriptionHandle);
    +        subscriptionHandle = null;
    +        evtSubscribeCallback = null;
    +        if (!renderedXMLs.isEmpty()) {
    +            if (sessionFactory != null) {
    +                getLogger().info("Finishing processing leftover events");
    +                ProcessSession session = sessionFactory.createSession();
    +                processQueue(session);
    +            } else {
    +                throw new ProcessException("Stopping the processor but 
there is no ProcessSessionFactory stored and there are messages in the internal 
queue. Removing the processor now will " +
    +                        "clear the queue but will result in DATA LOSS. 
This is normally due to starting the processor, receiving events and stopping 
before the onTrigger happens. The messages " +
    +                        "in the internal queue cannot finish processing 
until until the processor is triggered to run.");
    +            }
    +        }
    +        sessionFactory = null;
    +        provenanceUri = null;
    +        renderedXMLs = null;
    +    }
    +
    +    @Override
    +    public void onTrigger(ProcessContext context, ProcessSessionFactory 
sessionFactory) throws ProcessException {
    +        this.sessionFactory = sessionFactory;
    --- End diff --
    
    This doesn't need to be set every time. Can just check if it's null and set 
it if true.


> JNA-Based Event Log Subscription Processor
> ------------------------------------------
>
>                 Key: NIFI-1976
>                 URL: https://issues.apache.org/jira/browse/NIFI-1976
>             Project: Apache NiFi
>          Issue Type: Sub-task
>            Reporter: Bryan Rosander
>
> Using JNA, we should be able to leverage existing Windows APIs to natively 
> consume events as they happen.  Will look into subscribing to events 
> (https://msdn.microsoft.com/en-us/library/windows/desktop/aa385771(v=vs.85).aspx)
>  in order to reduce latency.



--
This message was sent by Atlassian JIRA
(v6.3.4#6332)

Reply via email to