/**
* Copyright 2013-2014 Recruit Technologies Co., Ltd. and contributors
* (see CONTRIBUTORS.md)
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. A copy of the
* License is distributed with this work in the LICENSE.md file. You may
* also obtain a copy of the License from
*
* 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.gennai.gungnir.topology.operator.tuplejoin;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.gennai.gungnir.GungnirConfig;
import org.gennai.gungnir.topology.GungnirContext;
import org.gennai.gungnir.topology.operator.OperatorContext;
import org.gennai.gungnir.topology.processor.ProcessorException;
import org.gennai.gungnir.topology.processor.TtlCacheProcessor;
import org.gennai.gungnir.tuple.FieldAccessor;
import org.gennai.gungnir.tuple.GungnirTuple;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
public class JoinTupleCollection implements Cloneable {
private static final Logger LOG = LoggerFactory.getLogger(JoinTupleCollection.class);
public interface DispatchHandler {
void dispatch(List<Object> values);
}
private ComplexJoinContext complexContext;
private TtlCacheProcessor processor;
private int expireSecs;
private List<FieldAccessor> outputFields;
private DispatchHandler dispatchHandler;
private int seekSize;
private Map<String, TtlCacheProcessor> processorsMap;
private int[] outputFieldsIndex;
private boolean[] isOutputAllFields;
public JoinTupleCollection(ComplexJoinContext complexContext, TtlCacheProcessor processor,
int expireSecs, List<FieldAccessor> outputFields, int seekSize) {
this.complexContext = complexContext;
this.processor = processor;
this.expireSecs = expireSecs;
this.outputFields = outputFields;
this.seekSize = seekSize;
}
private JoinTupleCollection(JoinTupleCollection c) {
this.complexContext = c.complexContext;
this.processor = c.processor;
this.expireSecs = c.expireSecs;
this.outputFields = c.outputFields;
this.seekSize = c.seekSize;
this.processorsMap = c.processorsMap;
}
public void setDispatchHandler(DispatchHandler dispatchHandler) {
this.dispatchHandler = dispatchHandler;
}
public void prepare(GungnirConfig config, GungnirContext context,
OperatorContext operatorContext) {
if (processorsMap == null) {
processorsMap = Maps.newLinkedHashMap();
for (JoinContext joinContext : complexContext.getContexts()) {
TtlCacheProcessor processorCopy = processor.clone();
try {
processorCopy.open(config, context, operatorContext,
joinContext.getFromTuple().getTupleName(), expireSecs, seekSize);
} catch (ProcessorException e) {
LOG.error("Failed to open processor", e);
}
processorsMap.put(joinContext.getFromTuple().getTupleName(), processorCopy);
}
}
}
@SuppressWarnings("unchecked")
public List<Object> select(List<Object> values) {
if (outputFields == null) {
return values;
}
if (outputFieldsIndex == null) {
outputFieldsIndex = complexContext.getFieldsIndex(outputFields);
isOutputAllFields = new boolean[outputFields.size()];
for (int i = 0; i < outputFields.size(); i++) {
if ("*".equals(outputFields.get(i).getFieldName())) {
isOutputAllFields[i] = true;
}
}
}
List<Object> selectValues = Lists.newArrayList();
for (int i = 0; i < outputFieldsIndex.length; i++) {
if (outputFieldsIndex[i] >= 0) {
if (isOutputAllFields[i]) {
selectValues.addAll((List<Object>) values.get(outputFieldsIndex[i]));
} else {
selectValues.add(values.get(outputFieldsIndex[i]));
}
} else {
selectValues.add(null);
}
}
return selectValues;
}
private void join(List<List<List<Object>>> valuesList, int index, List<Object> values) {
for (int i = 0; i < valuesList.get(index).size(); i++) {
List<Object> newValues;
if (values != null) {
newValues = Lists.newArrayList(values);
} else {
newValues = Lists.newArrayList();
}
newValues.addAll(valuesList.get(index).get(i));
if (index < processorsMap.size() - 1) {
join(valuesList, index + 1, newValues);
} else {
dispatchHandler.dispatch(select(newValues));
}
}
}
private void join(Object key) {
boolean entired = true;
for (TtlCacheProcessor p : processorsMap.values()) {
if (p.size(key) == 0) {
entired = false;
break;
}
}
if (entired) {
List<List<List<Object>>> valuesList = Lists.newArrayListWithCapacity(processorsMap.size());
for (TtlCacheProcessor p : processorsMap.values()) {
valuesList.add(p.take(key));
}
join(valuesList, 0, null);
}
}
public void put(GungnirTuple tuple) {
JoinContext joinContext = complexContext.getContext(tuple.getTupleName());
Object key = joinContext.getKey(tuple);
if (LOG.isDebugEnabled()) {
LOG.debug("key:{}, fields: {}, tuple:{}", key, joinContext.getFields(), tuple);
}
try {
processorsMap.get(tuple.getTupleName()).put(key, joinContext.getValues(tuple));
} catch (ProcessorException e) {
LOG.error("Failed to store tuple", e);
}
join(key);
}
public void cleanup() {
if (processorsMap != null) {
for (Iterator<Map.Entry<String, TtlCacheProcessor>> it = processorsMap.entrySet().
iterator(); it.hasNext();) {
Map.Entry<String, TtlCacheProcessor> entry = it.next();
entry.getValue().close();
it.remove();
}
processorsMap = null;
}
}
@Override
public JoinTupleCollection clone() {
return new JoinTupleCollection(this);
}
}