/* * 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.nifi.remote.codec; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.nifi.remote.StandardVersionNegotiator; import org.apache.nifi.remote.VersionNegotiator; import org.apache.nifi.remote.exception.ProtocolException; import org.apache.nifi.remote.protocol.DataPacket; import org.apache.nifi.remote.util.StandardDataPacket; import org.apache.nifi.stream.io.StreamUtils; public class StandardFlowFileCodec implements FlowFileCodec { public static final int MAX_NUM_ATTRIBUTES = 25000; public static final String DEFAULT_FLOWFILE_PATH = "./"; private final VersionNegotiator versionNegotiator; public StandardFlowFileCodec() { versionNegotiator = new StandardVersionNegotiator(1); } @Override public void encode(final DataPacket dataPacket, final OutputStream encodedOut) throws IOException { final DataOutputStream out = new DataOutputStream(encodedOut); final Map<String, String> attributes = dataPacket.getAttributes(); out.writeInt(attributes.size()); for (final Map.Entry<String, String> entry : attributes.entrySet()) { writeString(entry.getKey(), out); writeString(entry.getValue(), out); } out.writeLong(dataPacket.getSize()); final InputStream in = dataPacket.getData(); StreamUtils.copy(in, encodedOut); encodedOut.flush(); } @Override public DataPacket decode(final InputStream stream) throws IOException, ProtocolException { final DataInputStream in = new DataInputStream(stream); final int numAttributes; try { numAttributes = in.readInt(); } catch (final EOFException e) { // we're out of data. return null; } // This is here because if the stream is not properly formed, we could get up to Integer.MAX_VALUE attributes, which will // generally result in an OutOfMemoryError. if (numAttributes > MAX_NUM_ATTRIBUTES) { throw new ProtocolException("FlowFile exceeds maximum number of attributes with a total of " + numAttributes); } final Map<String, String> attributes = new HashMap<>(numAttributes); for (int i = 0; i < numAttributes; i++) { final String attrName = readString(in); final String attrValue = readString(in); attributes.put(attrName, attrValue); } final long numBytes = in.readLong(); return new StandardDataPacket(attributes, stream, numBytes); } private void writeString(final String val, final DataOutputStream out) throws IOException { final byte[] bytes = val.getBytes("UTF-8"); out.writeInt(bytes.length); out.write(bytes); } private String readString(final DataInputStream in) throws IOException { final int numBytes = in.readInt(); final byte[] bytes = new byte[numBytes]; StreamUtils.fillBuffer(in, bytes, true); return new String(bytes, "UTF-8"); } @Override public List<Integer> getSupportedVersions() { return versionNegotiator.getSupportedVersions(); } @Override public VersionNegotiator getVersionNegotiator() { return versionNegotiator; } @Override public String toString() { return "Standard FlowFile Codec, Version " + versionNegotiator.getVersion(); } @Override public String getResourceName() { return "StandardFlowFileCodec"; } }