/*
* #!
* %
* Copyright (C) 2014 - 2016 Humboldt-Universität zu Berlin
* %
* Licensed 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 de.hub.cs.dbis.lrb.operators;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;
import backtype.storm.spout.SpoutOutputCollector;
import backtype.storm.task.TopologyContext;
import backtype.storm.topology.OutputFieldsDeclarer;
import backtype.storm.tuple.Fields;
import backtype.storm.tuple.Values;
import de.hub.cs.dbis.aeolus.spouts.AbstractOrderedFileInputSpout;
import de.hub.cs.dbis.aeolus.utils.TimestampMerger;
/**
* {@link FileReaderSpout} reads LRB stream data from multiple files. See {@link AbstractOrderedFileInputSpout} for
* configuring information of input files. Per default, a single file named {@code xway} is used.<br/>
* <br />
* Each file is expected to contain data of a single express way, including position records, and historical query
* request (ie, account balance, daily expenditure, and travel time query request). Each line must contain a single
* record and all records in each file must be in ascending timestamp order.<br />
* <br />
* <strong>Expected file format:</strong>{@code <Type,Time,remaining-attributes>}<br />
* where {@code Type} specifies the record type (valid values are 0, 2, 3, or 4) and {@code Time} is the timestamp
* attribute of the record. The number of remaining attributes depends on the record type. The type attribute and the
* remaining attributes are ignored while parsing an input record.
*
* @author mjsax
*/
public class FileReaderSpout extends AbstractOrderedFileInputSpout {
private final static long serialVersionUID = 8536833427236986702L;
/** The prefix of all input file names. */
private final String defaultPrefix = "xway";
/** The output collector to be used. */
private SpoutOutputCollector collector;
/**
* Instantiates a new {@link FileReaderSpout} that emits to the default output stream.
*/
public FileReaderSpout() {
super();
}
/**
* Instantiates a new {@link FileReaderSpout} that emits to the specified output stream.
*
* @param streamID
* The name of the used output stream.
*/
public FileReaderSpout(String streamID) {
super(streamID);
}
@Override
public void open(@SuppressWarnings("rawtypes") Map conf, TopologyContext context, SpoutOutputCollector collector) {
@SuppressWarnings("unchecked")
HashMap<Object, Object> newConf = new HashMap<Object, Object>(conf);
if(newConf.get(INPUT_FILE_NAME) == null) {
newConf.put(INPUT_FILE_NAME, this.defaultPrefix);
}
super.open(newConf, context, collector);
this.collector = collector;
}
/**
* {@inheritDoc} <br/>
* <br/>
* Each {@code line} is expected to be in CSV format having the timestamp attribute at the second position.
*/
@Override
protected long extractTimestamp(String line) throws ParseException {
int p1 = line.indexOf(",");
int p2 = line.indexOf(",", p1 + 1);
try {
return Long.parseLong(line.substring(p1 + 1, p2));
} catch(NumberFormatException e) {
throw new ParseException(e.getMessage(), p1);
} catch(IndexOutOfBoundsException e) {
if(p1 < 0) {
throw new ParseException("Input string has wrong format. Missing ','.", p1);
} else {
assert (p2 < 0);
throw new ParseException("Input string has wrong format. Missing second ','.", p2);
}
}
}
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer) {
super.declareOutputFields(declarer);
declarer.declareStream(TimestampMerger.FLUSH_STREAM_ID, new Fields("ts"));
}
@Override
public void activate() {/* empty */}
@Override
public void deactivate() {
this.collector.emit(TimestampMerger.FLUSH_STREAM_ID, new Values((Object)null));
}
@Override
public void ack(Object msgId) {/* empty */}
@Override
public void fail(Object msgId) {/* empty */}
@Override
public Map<String, Object> getComponentConfiguration() {
return null;
}
}