/* * 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.flume.sink.solr.morphline; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import org.apache.flume.Context; import org.apache.flume.Event; import org.apache.flume.FlumeException; import org.apache.flume.event.EventBuilder; import org.apache.flume.interceptor.Interceptor; import com.cloudera.cdk.morphline.api.Command; import com.cloudera.cdk.morphline.api.Record; import com.cloudera.cdk.morphline.base.Fields; import com.google.common.base.Preconditions; import com.google.common.io.ByteStreams; /** * Flume Interceptor that executes a morphline on events that are intercepted. * * Currently, there is a restriction in that the morphline must not generate more than one output * record for each input event. */ public class MorphlineInterceptor implements Interceptor { private final Context context; private final BlockingQueue<LocalMorphlineInterceptor> pool = new LinkedBlockingQueue(); protected MorphlineInterceptor(Context context) { Preconditions.checkNotNull(context); this.context = context; returnToPool(new LocalMorphlineInterceptor(context)); // fail fast on morphline compilation exception } @Override public void initialize() { } @Override public void close() { List<LocalMorphlineInterceptor> interceptors = new ArrayList(); pool.drainTo(interceptors); for (LocalMorphlineInterceptor interceptor : interceptors) { interceptor.close(); } } @Override public List<Event> intercept(List<Event> events) { LocalMorphlineInterceptor interceptor = borrowFromPool(); List<Event> results = interceptor.intercept(events); returnToPool(interceptor); return results; } @Override public Event intercept(Event event) { LocalMorphlineInterceptor interceptor = borrowFromPool(); Event result = interceptor.intercept(event); returnToPool(interceptor); return result; } private void returnToPool(LocalMorphlineInterceptor interceptor) { try { pool.put(interceptor); } catch (InterruptedException e) { throw new FlumeException(e); } } private LocalMorphlineInterceptor borrowFromPool() { LocalMorphlineInterceptor interceptor = pool.poll(); if (interceptor == null) { interceptor = new LocalMorphlineInterceptor(context); } return interceptor; } /////////////////////////////////////////////////////////////////////////////// // Nested classes: /////////////////////////////////////////////////////////////////////////////// /** Builder implementations MUST have a public no-arg constructor */ public static class Builder implements Interceptor.Builder { private Context context; public Builder() { } @Override public MorphlineInterceptor build() { return new MorphlineInterceptor(context); } @Override public void configure(Context context) { this.context = context; } } /////////////////////////////////////////////////////////////////////////////// // Nested classes: /////////////////////////////////////////////////////////////////////////////// private static final class LocalMorphlineInterceptor implements Interceptor { private final MorphlineHandlerImpl morphline; private final Collector collector; protected LocalMorphlineInterceptor(Context context) { this.morphline = new MorphlineHandlerImpl(); this.collector = new Collector(); this.morphline.setFinalChild(collector); this.morphline.configure(context); } @Override public void initialize() { } @Override public void close() { morphline.stop(); } @Override public List<Event> intercept(List<Event> events) { List results = new ArrayList(events.size()); for (Event event : events) { event = intercept(event); if (event != null) { results.add(event); } } return results; } @Override public Event intercept(Event event) { collector.reset(); morphline.process(event); List<Record> results = collector.getRecords(); if (results.size() == 0) { return null; } if (results.size() > 1) { throw new FlumeException(getClass().getName() + " must not generate more than one output record per input event"); } Event result = toEvent(results.get(0)); return result; } private Event toEvent(Record record) { Map<String, String> headers = new HashMap(); Map<String, Collection<Object>> recordMap = record.getFields().asMap(); byte[] body = null; for (Map.Entry<String, Collection<Object>> entry : recordMap.entrySet()) { if (entry.getValue().size() > 1) { throw new FlumeException(getClass().getName() + " must not generate more than one output value per record field"); } assert entry.getValue().size() != 0; // guava guarantees that Object firstValue = entry.getValue().iterator().next(); if (Fields.ATTACHMENT_BODY.equals(entry.getKey())) { if (firstValue instanceof byte[]) { body = (byte[]) firstValue; } else if (firstValue instanceof InputStream) { try { body = ByteStreams.toByteArray((InputStream) firstValue); } catch (IOException e) { throw new FlumeException(e); } } else { throw new FlumeException(getClass().getName() + " must non generate attachments that are not a byte[] or InputStream"); } } else { headers.put(entry.getKey(), firstValue.toString()); } } return EventBuilder.withBody(body, headers); } } /////////////////////////////////////////////////////////////////////////////// // Nested classes: /////////////////////////////////////////////////////////////////////////////// private static final class Collector implements Command { private final List<Record> results = new ArrayList(); public List<Record> getRecords() { return results; } public void reset() { results.clear(); } @Override public Command getParent() { return null; } @Override public void notify(Record notification) { } @Override public boolean process(Record record) { Preconditions.checkNotNull(record); results.add(record); return true; } } }