/**
* 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 com.pinterest.terrapin.zookeeper;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.pinterest.terrapin.TerrapinUtil;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.helix.model.ExternalView;
import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.annotate.JsonProperty;
import org.codehaus.jackson.map.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.util.Map;
import java.util.TreeMap;
import java.util.Collections;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
/**
* Contains a mapping from partition to list of instances serving the partition. This
* is needed to reduce amount of data being transferred from zookeeper. It serializes
* The state as compressed JSON.
*/
public class ViewInfo {
private static final Logger LOG = LoggerFactory.getLogger(ViewInfo.class);
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@JsonProperty
private final String resource;
@JsonProperty
private Map<String, List<String>> partitionMap;
public ViewInfo() {
this.resource = null;
this.partitionMap = null;
}
public ViewInfo(ExternalView externalView) {
this.resource = externalView.getResourceName();
Set<String> partitionSet = externalView.getPartitionSet();
this.partitionMap = Maps.newTreeMap();
for (String partition : partitionSet) {
Map<String, String> stateMap = externalView.getStateMap(partition);
Pair<String, Integer> resourceAndPartitionNum =
TerrapinUtil.getResourceAndPartitionNum(partition);
if (resourceAndPartitionNum == null) {
LOG.warn("Invalid helix partition for " + resource + " : " + partition);
continue;
}
if (stateMap == null) {
continue;
}
List<String> instanceList = Lists.newArrayListWithCapacity(5);
for (Map.Entry<String, String> entry : stateMap.entrySet()) {
if (entry.getValue().equals("ONLINE")) {
instanceList.add(entry.getKey());
}
}
// Keep the list in alphabetical order.
if (!instanceList.isEmpty()) {
Collections.sort(instanceList);
this.partitionMap.put(TerrapinUtil.getViewPartitionName(resource,
resourceAndPartitionNum.getRight()), instanceList);
}
}
}
public static ViewInfo fromJson(byte[] json) throws Exception {
ViewInfo viewInfo = OBJECT_MAPPER.readValue(json, ViewInfo.class);
for (Map.Entry<String, List<String>> entry : viewInfo.partitionMap.entrySet()) {
Collections.sort(entry.getValue());
}
return viewInfo;
}
/**
* Returns the set of instances for a partition. Note that this would
*/
public List<String> getInstancesForPartition(String partition) {
if (this.partitionMap == null || !this.partitionMap.containsKey(partition)) {
return Lists.newArrayListWithCapacity(0);
}
return partitionMap.get(partition);
}
public boolean hasOnlinePartitions() {
return partitionMap != null && partitionMap.size() > 0;
}
@JsonIgnore
public int getNumOnlinePartitions() {
return partitionMap == null ? 0 : partitionMap.size();
}
@JsonIgnore
public String getResource() {
return resource;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof ViewInfo)) {
return false;
}
ViewInfo viewInfo = (ViewInfo)o;
if (!resource.equals(viewInfo.resource)) {
return false;
}
return partitionMap.equals(viewInfo.partitionMap);
}
public byte[] toJson() throws Exception {
return OBJECT_MAPPER.writeValueAsBytes(this);
}
public static ViewInfo fromCompressedJson(byte[] compressedJson) throws Exception {
ByteArrayInputStream in = new ByteArrayInputStream(compressedJson);
GZIPInputStream zipIs = new GZIPInputStream(in);
ViewInfo viewInfo = fromJson(IOUtils.toByteArray(zipIs));
in.close();
zipIs.close();
return viewInfo;
}
public byte[] toCompressedJson() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
GZIPOutputStream zipOs = new GZIPOutputStream(out);
zipOs.write(this.toJson());
zipOs.close();
byte[] data = out.toByteArray();
return data;
}
/**
* To pretty printing JSON format with simplified partition key. The returned JSON string is a
* ordered object sort by partition key. This function is only used for displaying status in
* the html page
* @return JSON string
* @throws IOException if the dumping process raises any exceptions
*/
public String toPrettyPrintingJson() throws IOException {
Map<Integer, List<String>> simplifiedPartitionMap = new TreeMap<Integer, List<String>>();
for (Map.Entry<String, List<String>> entry : partitionMap.entrySet()) {
String longPartitionName = entry.getKey();
int partitionNumber = TerrapinUtil.getViewPartitionNumber(longPartitionName);
simplifiedPartitionMap.put(partitionNumber, entry.getValue());
}
return OBJECT_MAPPER.defaultPrettyPrintingWriter().writeValueAsString(simplifiedPartitionMap);
}
/**
* Check partition is online or not
* @param partition partition number
* @return true if partition is online, otherwise, return false
*/
public boolean isOnlinePartition(String partition) {
return partitionMap != null && partitionMap.containsKey(partition);
}
}