package com.linkedin.databus.core; /* * * Copyright 2013 LinkedIn Corp. All rights reserved * * 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. * */ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.security.spec.InvalidParameterSpecException; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import org.apache.log4j.Logger; import org.codehaus.jackson.JsonGenerationException; import org.codehaus.jackson.JsonParseException; import org.codehaus.jackson.map.JsonMappingException; import org.codehaus.jackson.map.ObjectMapper; import com.linkedin.databus.core.data_model.PhysicalPartition; import com.linkedin.databus.core.util.StringUtils; /** * Constructs a checkpoint for multiple buffers. * Essentially it is a list of single buffer checkpoints mapped by physical partition * */ public class CheckpointMult { public static final String MODULE = DbusEventBufferMult.class.getName(); public static final Logger LOG = Logger.getLogger(MODULE); private final Map<PhysicalPartition, Checkpoint> _pPart2Checkpoint = new HashMap<PhysicalPartition, Checkpoint>(); private static final ObjectMapper _mapper = new ObjectMapper(); private static final String CURSOR_PARTITION_KEY = "cursorPartition"; /** * _cursorPartition has the last partition from which an event was sent (could be partial or full window) * to the receiver over a channel. * * To avoid upgrade problems, we do not serialize _cursorPartition in the map, but we deserialize it if * we see it. * * The cursorPartition is only used as a hint, and its absence will not affect the correctness. Specifically, * it is expected that the cursorPartition is ignored if any one of the checkpoints indicates that a partial * window was sent. */ private PhysicalPartition _cursorPartition = null; public PhysicalPartition getPartialWindowPartition() { return _partialWindowPartition; } /** * _partialWindowPartition has the partition that has a partial window consumed. If it is non-null, it means we need * to start streaming from this partition. * * If _partialWindowPartition is null, then there was no partition in which a partial window was consumed and streaming * can start from any partition. */ private PhysicalPartition _partialWindowPartition = null; public CheckpointMult() { } /** * reconstruct Mult checkpoint from a string representation * @param checkpointString checkpoint serialization string * @return CheckpointMult object */ @SuppressWarnings("unchecked") public CheckpointMult(String checkpointString) throws JsonParseException, JsonMappingException, InvalidParameterSpecException, IOException { if (null != checkpointString) { // json returns Map between "pSrcId" and 'serialized string' of Checkpoint Map<String, String> map = _mapper.readValue( new ByteArrayInputStream(checkpointString.getBytes(Charset.defaultCharset())), Map.class); boolean debugEnabled = LOG.isDebugEnabled(); for(Entry<String, String> m : map.entrySet()) { if (m.getKey().equals(CURSOR_PARTITION_KEY)) { _cursorPartition = PhysicalPartition.createFromJsonString(m.getValue()); continue; } else if (!m.getKey().startsWith("{")) { // Ignore anything we don't understand. if (debugEnabled) { LOG.debug("Ignoring checkpoint mult key" + m.getKey()); } continue; } PhysicalPartition pPart = PhysicalPartition.createFromJsonString(m.getKey()); String cpString = m.getValue();//serialized checkpoint Checkpoint cp = new Checkpoint(cpString); if(debugEnabled) LOG.debug("CPMULT constructor: pPart="+pPart + ";cp="+cp); _pPart2Checkpoint.put(pPart, cp); if (cp.isPartialWindow()) { if (_partialWindowPartition != null) { throw new InvalidParameterSpecException("Multiple partitions with partial window:" + _partialWindowPartition.toSimpleString() + " and " + pPart.toSimpleString()); } _partialWindowPartition = pPart; } } } } /** * returns checkpoint for a specific physical partition * @param pPart * @return checkpoint for the partition */ public Checkpoint getCheckpoint(PhysicalPartition pPart) { return _pPart2Checkpoint.get(pPart); } /** * adds a new checkpoint * @param pPart * @param cp */ public void addCheckpoint(PhysicalPartition pPart, Checkpoint cp) { if (cp.isPartialWindow()) { if (_partialWindowPartition != null && !_partialWindowPartition.equals(pPart)) { throw new DatabusRuntimeException("Existing partition with partial window:" + _partialWindowPartition.toSimpleString() + ",cannot allow partition " + pPart.toSimpleString()); } _partialWindowPartition = pPart; } else { // We could be updating a checkpoint so that it is not partial window any more. if (_partialWindowPartition != null && pPart.equals(_partialWindowPartition)) { _partialWindowPartition = null; } } _pPart2Checkpoint.put(pPart, cp); } /** * serialize CheckpointMult into the stream * @param outStream */ void serialize(OutputStream outStream) throws JsonGenerationException, JsonMappingException, IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // first convert checkpointmult into a map Map<String, String> map = new HashMap<String, String>(); boolean debugEnabled = LOG.isDebugEnabled(); for(Entry<PhysicalPartition, Checkpoint> e: _pPart2Checkpoint.entrySet()) { baos.reset(); Checkpoint cp = e.getValue(); cp.serialize(baos); String pPartJson = e.getKey().toJsonString(); String cpStr = StringUtils.bytesToString(baos.toByteArray()); map.put(pPartJson, cpStr); if(debugEnabled) LOG.debug("phSourId=" + e.getKey() + ";cp =" + cpStr); } _mapper.writeValue(outStream, map); } @Override public String toString() { ByteArrayOutputStream bs = new ByteArrayOutputStream(); try { serialize(bs); } catch (IOException e) { LOG.warn("toString failed", e); } try { return bs.toString("UTF-8"); } catch (UnsupportedEncodingException e) { return "InvalidSerialization"; } } public int getNumCheckponts() { return _pPart2Checkpoint.size(); } public PhysicalPartition getCursorPartition() { return _cursorPartition; } public void setCursorPartition(PhysicalPartition cursorPartition) { _cursorPartition = cursorPartition; } @Override public boolean equals(Object other) { if (null == other) return false; if (this == other) return true; if (!(other instanceof CheckpointMult)) return false; CheckpointMult otherCp = (CheckpointMult)other; boolean success = _pPart2Checkpoint.equals(otherCp._pPart2Checkpoint); return success; } @Override public int hashCode() { return _pPart2Checkpoint.hashCode(); //NOTE: _cursorPartition is ignored because it is optional } }