/** Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved. Contact: SYSTAP, LLC DBA Blazegraph 2501 Calvert ST NW #106 Washington, DC 20008 licenses@blazegraph.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * Created on Aug 25, 2010 */ package com.bigdata.bop.bset; import java.util.Arrays; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; import com.bigdata.bop.BOp; import com.bigdata.bop.BOpContext; import com.bigdata.bop.IBindingSet; import com.bigdata.bop.IConstraint; import com.bigdata.bop.NV; import com.bigdata.bop.PipelineOp; import com.bigdata.bop.engine.BOpStats; import com.bigdata.relation.accesspath.IBlockingBuffer; import cutthecrap.utils.striterators.ICloseableIterator; /** * An operator for conditional routing of binding sets in a pipeline. The * operator will copy binding sets either to the default sink (if a condition is * satisfied) and otherwise to the alternate sink (iff one is specified). If a * solution fails the constraint and the alternate sink is not specified, then * the solution is dropped. * <p> * Conditional routing can be useful where a different data flow is required * based on the type of an object (for example a term identifier versus an * inline term in the RDF database) or where there is a need to jump around a * join group based on some condition. * <p> * Conditional routing will cause reordering of solutions when the alternate * sink is specified as some solutions will flow to the primary sink while * others flow to the alterate sink. * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * @version $Id: ConditionalRoutingOp.java 7773 2014-01-11 12:49:05Z thompsonbry * $ */ public class ConditionalRoutingOp extends PipelineOp { /** * */ private static final long serialVersionUID = 1L; public interface Annotations extends PipelineOp.Annotations { /** * An {@link IConstraint} which specifies the condition. When the * condition is satisfied the binding set is routed to the default sink. * When the condition is not satisfied, the binding set is routed to the * alternative sink. */ String CONDITION = ConditionalRoutingOp.class.getName() + ".condition"; } /** * Deep copy constructor. * * @param op */ public ConditionalRoutingOp(final ConditionalRoutingOp op) { super(op); } /** * Shallow copy constructor. * * @param args * @param annotations */ public ConditionalRoutingOp(final BOp[] args, final Map<String, Object> annotations) { super(args, annotations); } public ConditionalRoutingOp(final BOp[] args, final NV... anns) { this(args, NV.asMap(anns)); } /** * @see Annotations#CONDITION */ public IConstraint getCondition() { return (IConstraint) getProperty(Annotations.CONDITION); } @Override public FutureTask<Void> eval(final BOpContext<IBindingSet> context) { return new FutureTask<Void>(new ConditionalRouteTask(this, context)); } /** * Copy the source to the sink or the alternative sink depending on the * condition. */ static private class ConditionalRouteTask implements Callable<Void> { private final BOpStats stats; private final IConstraint condition; private final ICloseableIterator<IBindingSet[]> source; private final IBlockingBuffer<IBindingSet[]> sink; private final IBlockingBuffer<IBindingSet[]> sink2; ConditionalRouteTask(final ConditionalRoutingOp op, final BOpContext<IBindingSet> context) { this.stats = context.getStats(); this.condition = op.getCondition(); if (condition == null) throw new IllegalArgumentException(); this.source = context.getSource(); this.sink = context.getSink(); this.sink2 = context.getSink2(); // MAY be null. // if (sink2 == null) // throw new IllegalArgumentException(); if (sink == sink2) throw new IllegalArgumentException(); } @Override public Void call() throws Exception { try { while (source.hasNext()) { final IBindingSet[] chunk = source.next(); stats.chunksIn.increment(); stats.unitsIn.add(chunk.length); final IBindingSet[] def = new IBindingSet[chunk.length]; final IBindingSet[] alt = sink2 == null ? null : new IBindingSet[chunk.length]; int ndef = 0, nalt = 0; for (int i = 0; i < chunk.length; i++) { if (i % 20 == 0 && Thread.interrupted()) { // Eagerly notice if the operator is interrupted. throw new RuntimeException( new InterruptedException()); } final IBindingSet bset = chunk[i].clone(); if (condition.accept(bset)) { // solution passes condition. default sink. def[ndef++] = bset; } else if (sink2 != null) { // solution fails condition. alternative sink. alt[nalt++] = bset; } } if (ndef > 0) { if (ndef == def.length) sink.add(def); else sink.add(Arrays.copyOf(def, ndef)); // stats.chunksOut.increment(); // stats.unitsOut.add(ndef); } if (nalt > 0 && sink2 != null) { if (nalt == alt.length) sink2.add(alt); else sink2.add(Arrays.copyOf(alt, nalt)); // stats.chunksOut.increment(); // stats.unitsOut.add(nalt); } } sink.flush(); if (sink2 != null) sink2.flush(); return null; } finally { source.close(); sink.close(); if (sink2 != null) sink2.close(); } } // call() } // ConditionalRoutingTask. }