/* * Copyright © 2015 Cask Data, Inc. * * 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 co.cask.cdap.data.stream.service.upload; import co.cask.cdap.common.NotFoundException; import co.cask.cdap.common.io.Locations; import co.cask.cdap.data.stream.StreamDataFileConstants; import co.cask.cdap.data.stream.StreamDataFileWriter; import co.cask.cdap.data.stream.service.ConcurrentStreamWriter; import co.cask.cdap.data.stream.service.MutableStreamEvent; import co.cask.cdap.data.stream.service.MutableStreamEventData; import co.cask.cdap.data2.transaction.stream.StreamConfig; import com.google.common.base.Throwables; import com.google.common.collect.Maps; import com.google.common.io.Closeables; import org.apache.twill.filesystem.Location; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Iterator; import java.util.Map; /** * Implementation of {@link ContentWriter} that writes to stream file directly. */ final class FileContentWriter implements ContentWriter { private final StreamConfig streamConfig; private final ConcurrentStreamWriter streamWriter; private final MutableStreamEventData streamEventData; private final MutableStreamEvent streamEvent; private final Location eventFile; private final Location indexFile; private final StreamDataFileWriter writer; private long eventCount; FileContentWriter(StreamConfig streamConfig, ConcurrentStreamWriter streamWriter, Location directory, Map<String, String> headers) throws IOException { this.streamConfig = streamConfig; this.streamWriter = streamWriter; this.streamEventData = new MutableStreamEventData(); this.streamEvent = new MutableStreamEvent(); directory.mkdirs(); this.eventFile = directory.append("upload.dat"); this.indexFile = directory.append("upload.idx"); Map<String, String> properties = createStreamFileProperties(headers); properties.put(StreamDataFileConstants.Property.Key.UNI_TIMESTAMP, StreamDataFileConstants.Property.Value.CLOSE_TIMESTAMP); this.writer = new StreamDataFileWriter(Locations.newOutputSupplier(eventFile), Locations.newOutputSupplier(indexFile), streamConfig.getIndexInterval(), properties); } private Map<String, String> createStreamFileProperties(Map<String, String> headers) { // Prepend "event." to each header key Map<String, String> properties = Maps.newHashMap(); for (Map.Entry<String, String> entry : headers.entrySet()) { properties.put(StreamDataFileConstants.Property.Key.EVENT_HEADER_PREFIX + entry.getKey(), entry.getValue()); } return properties; } @Override public void append(ByteBuffer body, boolean immutable) throws IOException { doAppend(body, System.currentTimeMillis()); } @Override public void appendAll(Iterator<ByteBuffer> bodies, boolean immutable) throws IOException { long timestamp = System.currentTimeMillis(); while (bodies.hasNext()) { doAppend(bodies.next(), timestamp); } } @Override public void cancel() { Closeables.closeQuietly(writer); Locations.deleteQuietly(Locations.getParent(eventFile), true); } @Override public void close() throws IOException { try { writer.flush(); streamWriter.appendFile(streamConfig.getStreamId(), eventFile, indexFile, eventCount, writer); } catch (NotFoundException e) { throw Throwables.propagate(e); } finally { Locations.deleteQuietly(Locations.getParent(eventFile), true); } } private void doAppend(ByteBuffer body, long timestamp) throws IOException { writer.append(streamEvent.set(streamEventData.setBody(body), timestamp)); eventCount++; } }