/*
* 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.flink.storm.wrappers;
/*
* We do neither import
* org.apache.storm.tuple.Tuple;
* nor
* org.apache.flink.api.java.tuple.Tuple
* to avoid confusion
*/
import org.apache.storm.generated.GlobalStreamId;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.MessageId;
import org.apache.storm.tuple.Values;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
/**
* {@link StormTuple} converts a Flink tuple of type {@code IN} into a Storm tuple.
*/
public class StormTuple<IN> implements org.apache.storm.tuple.Tuple {
/** The Storm representation of the original Flink tuple. */
private final Values stormTuple;
/** The schema (ie, ordered field names) of this tuple. */
private final Fields schema;
/** The task ID where this tuple was produced. */
private final int producerTaskId;
/** The input stream from which this tuple was received. */
private final String producerStreamId;
/** The producer's component ID of this tuple. */
private final String producerComponentId;
/** The message that is associated with this tuple. */
private final MessageId messageId;
/**
* Create a new Storm tuple from the given Flink tuple.
*
* @param flinkTuple
* The Flink tuple to be converted.
* @param schema
* The schema (ie, ordered field names) of the tuple.
* @param producerTaskId
* The task ID of the producer (a valid, ie, non-negative ID, implicates the truncation of the last
* attribute of {@code flinkTuple}).
* @param producerStreamId
* The input stream ID from which this tuple was received.
* @param producerComponentId
* The component ID of the producer.
* @param messageId
* The message ID of this tuple.
*/
public StormTuple(final IN flinkTuple, final Fields schema, final int producerTaskId, final String producerStreamId, final String producerComponentId, final MessageId messageId) {
if (flinkTuple instanceof org.apache.flink.api.java.tuple.Tuple) {
final org.apache.flink.api.java.tuple.Tuple t = (org.apache.flink.api.java.tuple.Tuple) flinkTuple;
final int numberOfAttributes;
// does flinkTuple carry producerTaskId as last attribute?
if (producerTaskId < 0) {
numberOfAttributes = t.getArity();
} else {
numberOfAttributes = t.getArity() - 1;
}
this.stormTuple = new Values();
for (int i = 0; i < numberOfAttributes; ++i) {
this.stormTuple.add(t.getField(i));
}
} else {
this.stormTuple = new Values(flinkTuple);
}
this.schema = schema;
this.producerTaskId = producerTaskId;
this.producerStreamId = producerStreamId;
this.producerComponentId = producerComponentId;
this.messageId = messageId;
}
@Override
public int size() {
return this.stormTuple.size();
}
@Override
public boolean contains(final String field) {
if (this.schema != null) {
return this.schema.contains(field);
}
try {
this.getPublicMemberField(field);
return true;
} catch (NoSuchFieldException f) {
try {
this.getGetterMethod(field);
return true;
} catch (Exception g) {
return false;
}
} catch (Exception e) {
return false;
}
}
@Override
public Fields getFields() {
return this.schema;
}
@Override
public int fieldIndex(final String field) {
return this.schema.fieldIndex(field);
}
@Override
public List<Object> select(final Fields selector) {
return this.schema.select(selector, this.stormTuple);
}
@Override
public Object getValue(final int i) {
return this.stormTuple.get(i);
}
@Override
public String getString(final int i) {
return (String) this.stormTuple.get(i);
}
@Override
public Integer getInteger(final int i) {
return (Integer) this.stormTuple.get(i);
}
@Override
public Long getLong(final int i) {
return (Long) this.stormTuple.get(i);
}
@Override
public Boolean getBoolean(final int i) {
return (Boolean) this.stormTuple.get(i);
}
@Override
public Short getShort(final int i) {
return (Short) this.stormTuple.get(i);
}
@Override
public Byte getByte(final int i) {
return (Byte) this.stormTuple.get(i);
}
@Override
public Double getDouble(final int i) {
return (Double) this.stormTuple.get(i);
}
@Override
public Float getFloat(final int i) {
return (Float) this.stormTuple.get(i);
}
@Override
public byte[] getBinary(final int i) {
return (byte[]) this.stormTuple.get(i);
}
private Field getPublicMemberField(final String field) throws Exception {
assert (this.stormTuple.size() == 1);
return this.stormTuple.get(0).getClass().getField(field);
}
private Method getGetterMethod(final String field) throws Exception {
assert (this.stormTuple.size() == 1);
return this.stormTuple
.get(0)
.getClass()
.getMethod("get" + Character.toUpperCase(field.charAt(0)) + field.substring(1),
(Class<?>[]) null);
}
private Object getValueByPublicMember(final String field) throws Exception {
assert (this.stormTuple.size() == 1);
return getPublicMemberField(field).get(this.stormTuple.get(0));
}
private Object getValueByGetter(final String field) throws Exception {
assert (this.stormTuple.size() == 1);
return getGetterMethod(field).invoke(this.stormTuple.get(0), (Object[]) null);
}
@SuppressWarnings("unchecked")
public <T> T getValueByName(final String field) {
if (this.schema != null) {
return (T) this.getValue(this.schema.fieldIndex(field));
}
assert (this.stormTuple.size() == 1);
Exception e;
try {
// try public member
return (T) getValueByPublicMember(field);
} catch (NoSuchFieldException f) {
try {
// try getter-method
return (T) getValueByGetter(field);
} catch (Exception g) {
e = g;
}
} catch (Exception f) {
e = f;
}
throw new RuntimeException("Could not access field <" + field + ">", e);
}
@Override
public Object getValueByField(final String field) {
return getValueByName(field);
}
@Override
public String getStringByField(final String field) {
return getValueByName(field);
}
@Override
public Integer getIntegerByField(final String field) {
return getValueByName(field);
}
@Override
public Long getLongByField(final String field) {
return getValueByName(field);
}
@Override
public Boolean getBooleanByField(final String field) {
return getValueByName(field);
}
@Override
public Short getShortByField(final String field) {
return getValueByName(field);
}
@Override
public Byte getByteByField(final String field) {
return getValueByName(field);
}
@Override
public Double getDoubleByField(final String field) {
return getValueByName(field);
}
@Override
public Float getFloatByField(final String field) {
return getValueByName(field);
}
@Override
public byte[] getBinaryByField(final String field) {
return getValueByName(field);
}
@Override
public List<Object> getValues() {
return this.stormTuple;
}
@Override
public GlobalStreamId getSourceGlobalStreamid() {
return new GlobalStreamId(this.producerComponentId, this.producerStreamId);
}
@Override
public String getSourceComponent() {
return this.producerComponentId;
}
@Override
public int getSourceTask() {
return this.producerTaskId;
}
@Override
public String getSourceStreamId() {
return this.producerStreamId;
}
@Override
public MessageId getMessageId() {
return this.messageId;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((messageId == null) ? 0 : messageId.hashCode());
result = prime * result
+ ((producerComponentId == null) ? 0 : producerComponentId.hashCode());
result = prime * result + ((producerStreamId == null) ? 0 : producerStreamId.hashCode());
result = prime * result + producerTaskId;
result = prime * result + ((schema == null) ? 0 : schema.toList().hashCode());
result = prime * result + ((stormTuple == null) ? 0 : stormTuple.hashCode());
return result;
}
@SuppressWarnings("rawtypes")
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
StormTuple other = (StormTuple) obj;
if (messageId == null) {
if (other.messageId != null) {
return false;
}
} else if (!messageId.equals(other.messageId)) {
return false;
}
if (producerComponentId == null) {
if (other.producerComponentId != null) {
return false;
}
} else if (!producerComponentId.equals(other.producerComponentId)) {
return false;
}
if (producerStreamId == null) {
if (other.producerStreamId != null) {
return false;
}
} else if (!producerStreamId.equals(other.producerStreamId)) {
return false;
}
if (producerTaskId != other.producerTaskId) {
return false;
}
if (schema == null) {
if (other.schema != null) {
return false;
}
} else if (!schema.toList().equals(other.schema.toList())) {
return false;
}
if (stormTuple == null) {
if (other.stormTuple != null) {
return false;
}
} else if (!stormTuple.equals(other.stormTuple)) {
return false;
}
return true;
}
@Override
public String toString() {
return "StormTuple{ " + stormTuple.toString() + "[" + this.producerComponentId + ","
+ this.producerStreamId + "," + this.producerTaskId + "," + this.messageId + "]}";
}
@Override
public GlobalStreamId getSourceGlobalStreamId() {
return new GlobalStreamId(this.producerComponentId, this.producerStreamId);
}
}