package org.apache.bookkeeper.client;
/**
* 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.
*/
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import org.apache.bookkeeper.util.StringUtils;
import org.apache.log4j.Logger;
/**
* This class encapsulates all the ledger metadata that is persistently stored
* in zookeeper. It provides parsing and serialization methods of such metadata.
*
*/
public class LedgerMetadata {
static final Logger LOG = Logger.getLogger(LedgerMetadata.class);
private static final String closed = "CLOSED";
private static final String lSplitter = "\n";
private static final String tSplitter = "\t";
// can't use -1 for NOTCLOSED because that is reserved for a closed, empty
// ledger
public static final int NOTCLOSED = -101;
int ensembleSize;
int quorumSize;
long length;
long close;
private SortedMap<Long, ArrayList<InetSocketAddress>> ensembles = new TreeMap<Long, ArrayList<InetSocketAddress>>();
ArrayList<InetSocketAddress> currentEnsemble;
public LedgerMetadata(int ensembleSize, int quorumSize) {
this.ensembleSize = ensembleSize;
this.quorumSize = quorumSize;
/*
* It is set in PendingReadOp.readEntryComplete, and
* we read it in LedgerRecoveryOp.readComplete.
*/
this.length = 0;
this.close = NOTCLOSED;
};
private LedgerMetadata() {
this(0, 0);
}
/**
* Get the Map of bookie ensembles for the various ledger fragments
* that make up the ledger.
*
* @return SortedMap of Ledger Fragments and the corresponding
* bookie ensembles that store the entries.
*/
public SortedMap<Long, ArrayList<InetSocketAddress>> getEnsembles() {
return ensembles;
}
boolean isClosed() {
return close != NOTCLOSED;
}
void close(long entryId) {
close = entryId;
}
void addEnsemble(long startEntryId, ArrayList<InetSocketAddress> ensemble) {
assert ensembles.isEmpty() || startEntryId >= ensembles.lastKey();
ensembles.put(startEntryId, ensemble);
currentEnsemble = ensemble;
}
ArrayList<InetSocketAddress> getEnsemble(long entryId) {
// the head map cannot be empty, since we insert an ensemble for
// entry-id 0, right when we start
return ensembles.get(ensembles.headMap(entryId + 1).lastKey());
}
/**
* the entry id > the given entry-id at which the next ensemble change takes
* place ( -1 if no further ensemble changes)
*
* @param entryId
* @return
*/
long getNextEnsembleChange(long entryId) {
SortedMap<Long, ArrayList<InetSocketAddress>> tailMap = ensembles.tailMap(entryId + 1);
if (tailMap.isEmpty()) {
return -1;
} else {
return tailMap.firstKey();
}
}
/**
* Generates a byte array based on a LedgerConfig object received.
*
* @param config
* LedgerConfig object
* @return byte[]
*/
public byte[] serialize() {
StringBuilder s = new StringBuilder();
s.append(quorumSize).append(lSplitter).append(ensembleSize).append(lSplitter).append(length);
for (Map.Entry<Long, ArrayList<InetSocketAddress>> entry : ensembles.entrySet()) {
s.append(lSplitter).append(entry.getKey());
for (InetSocketAddress addr : entry.getValue()) {
s.append(tSplitter);
StringUtils.addrToString(s, addr);
}
}
if (close != NOTCLOSED) {
s.append(lSplitter).append(close).append(tSplitter).append(closed);
}
if (LOG.isDebugEnabled()) {
LOG.debug("Serialized config: " + s.toString());
}
return s.toString().getBytes();
}
/**
* Parses a given byte array and transforms into a LedgerConfig object
*
* @param array
* byte array to parse
* @return LedgerConfig
* @throws IOException
* if the given byte[] cannot be parsed
*/
static LedgerMetadata parseConfig(byte[] bytes) throws IOException {
LedgerMetadata lc = new LedgerMetadata();
String config = new String(bytes);
if (LOG.isDebugEnabled()) {
LOG.debug("Parsing Config: " + config);
}
String lines[] = config.split(lSplitter);
if (lines.length < 2) {
throw new IOException("Quorum size or ensemble size absent from config: " + config);
}
try {
lc.quorumSize = new Integer(lines[0]);
lc.ensembleSize = new Integer(lines[1]);
lc.length = new Long(lines[2]);
for (int i = 3; i < lines.length; i++) {
String parts[] = lines[i].split(tSplitter);
if (parts[1].equals(closed)) {
lc.close = new Long(parts[0]);
break;
}
ArrayList<InetSocketAddress> addrs = new ArrayList<InetSocketAddress>();
for (int j = 1; j < parts.length; j++) {
addrs.add(StringUtils.parseAddr(parts[j]));
}
lc.addEnsemble(new Long(parts[0]), addrs);
}
} catch (NumberFormatException e) {
throw new IOException(e);
}
return lc;
}
}