001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hdfs.server.namenode;
019
020import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_ADD;
021import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_ADD_BLOCK;
022import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_ADD_CACHE_DIRECTIVE;
023import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_ADD_CACHE_POOL;
024import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_ALLOCATE_BLOCK_ID;
025import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_ALLOW_SNAPSHOT;
026import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_CANCEL_DELEGATION_TOKEN;
027import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_CLEAR_NS_QUOTA;
028import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_CLOSE;
029import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_CONCAT_DELETE;
030import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_CREATE_SNAPSHOT;
031import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_DELETE;
032import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_DELETE_SNAPSHOT;
033import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_DISALLOW_SNAPSHOT;
034import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_END_LOG_SEGMENT;
035import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_GET_DELEGATION_TOKEN;
036import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_INVALID;
037import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_MKDIR;
038import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_MODIFY_CACHE_DIRECTIVE;
039import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_MODIFY_CACHE_POOL;
040import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_REASSIGN_LEASE;
041import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_REMOVE_CACHE_DIRECTIVE;
042import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_REMOVE_CACHE_POOL;
043import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_REMOVE_XATTR;
044import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_RENAME;
045import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_RENAME_OLD;
046import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_RENAME_SNAPSHOT;
047import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_RENEW_DELEGATION_TOKEN;
048import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_SET_ACL;
049import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_ROLLING_UPGRADE_FINALIZE;
050import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_ROLLING_UPGRADE_START;
051import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_SET_GENSTAMP_V1;
052import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_SET_GENSTAMP_V2;
053import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_SET_NS_QUOTA;
054import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_SET_OWNER;
055import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_SET_PERMISSIONS;
056import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_SET_QUOTA;
057import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_SET_REPLICATION;
058import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_SET_XATTR;
059import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_START_LOG_SEGMENT;
060import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_SYMLINK;
061import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_TIMES;
062import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_UPDATE_BLOCKS;
063import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_UPDATE_MASTER_KEY;
064import static org.apache.hadoop.hdfs.server.namenode.FSEditLogOpCodes.OP_SET_STORAGE_POLICY;
065
066import java.io.DataInput;
067import java.io.DataInputStream;
068import java.io.DataOutput;
069import java.io.DataOutputStream;
070import java.io.EOFException;
071import java.io.IOException;
072import java.util.ArrayList;
073import java.util.Arrays;
074import java.util.EnumMap;
075import java.util.List;
076import java.util.zip.CheckedInputStream;
077import java.util.zip.Checksum;
078
079import org.apache.commons.codec.DecoderException;
080import org.apache.commons.codec.binary.Hex;
081import org.apache.hadoop.classification.InterfaceAudience;
082import org.apache.hadoop.classification.InterfaceStability;
083import org.apache.hadoop.fs.ChecksumException;
084import org.apache.hadoop.fs.Options.Rename;
085import org.apache.hadoop.fs.XAttr;
086import org.apache.hadoop.fs.XAttrCodec;
087import org.apache.hadoop.fs.permission.AclEntry;
088import org.apache.hadoop.fs.permission.AclEntryScope;
089import org.apache.hadoop.fs.permission.AclEntryType;
090import org.apache.hadoop.fs.permission.FsAction;
091import org.apache.hadoop.fs.permission.FsPermission;
092import org.apache.hadoop.fs.permission.PermissionStatus;
093import org.apache.hadoop.hdfs.DFSConfigKeys;
094import org.apache.hadoop.hdfs.DeprecatedUTF8;
095import org.apache.hadoop.hdfs.protocol.Block;
096import org.apache.hadoop.hdfs.protocol.CacheDirectiveInfo;
097import org.apache.hadoop.hdfs.protocol.CachePoolInfo;
098import org.apache.hadoop.hdfs.protocol.ClientProtocol;
099import org.apache.hadoop.hdfs.protocol.HdfsConstants;
100import org.apache.hadoop.hdfs.protocol.LayoutVersion;
101import org.apache.hadoop.hdfs.protocol.LayoutVersion.Feature;
102import org.apache.hadoop.hdfs.protocol.proto.AclProtos.AclEditLogProto;
103import org.apache.hadoop.hdfs.protocol.proto.XAttrProtos.XAttrEditLogProto;
104import org.apache.hadoop.hdfs.protocolPB.PBHelper;
105import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier;
106import org.apache.hadoop.hdfs.server.blockmanagement.BlockStoragePolicySuite;
107import org.apache.hadoop.hdfs.util.XMLUtils;
108import org.apache.hadoop.hdfs.util.XMLUtils.InvalidXmlException;
109import org.apache.hadoop.hdfs.util.XMLUtils.Stanza;
110import org.apache.hadoop.io.ArrayWritable;
111import org.apache.hadoop.io.BytesWritable;
112import org.apache.hadoop.io.DataOutputBuffer;
113import org.apache.hadoop.io.IOUtils;
114import org.apache.hadoop.io.Text;
115import org.apache.hadoop.io.Writable;
116import org.apache.hadoop.io.WritableFactories;
117import org.apache.hadoop.io.WritableFactory;
118import org.apache.hadoop.ipc.ClientId;
119import org.apache.hadoop.ipc.RpcConstants;
120import org.apache.hadoop.security.token.delegation.DelegationKey;
121import org.apache.hadoop.util.DataChecksum;
122import org.xml.sax.ContentHandler;
123import org.xml.sax.SAXException;
124import org.xml.sax.helpers.AttributesImpl;
125
126import com.google.common.annotations.VisibleForTesting;
127import com.google.common.base.Joiner;
128import com.google.common.base.Preconditions;
129import com.google.common.collect.ImmutableMap;
130import com.google.common.collect.Lists;
131
132/**
133 * Helper classes for reading the ops from an InputStream.
134 * All ops derive from FSEditLogOp and are only
135 * instantiated from Reader#readOp()
136 */
137@InterfaceAudience.Private
138@InterfaceStability.Unstable
139public abstract class FSEditLogOp {
140  public final FSEditLogOpCodes opCode;
141  long txid = HdfsConstants.INVALID_TXID;
142  byte[] rpcClientId = RpcConstants.DUMMY_CLIENT_ID;
143  int rpcCallId = RpcConstants.INVALID_CALL_ID;
144
145  final public static class OpInstanceCache {
146    private final EnumMap<FSEditLogOpCodes, FSEditLogOp> inst =
147        new EnumMap<FSEditLogOpCodes, FSEditLogOp>(FSEditLogOpCodes.class);
148    
149    public OpInstanceCache() {
150      inst.put(OP_ADD, new AddOp());
151      inst.put(OP_CLOSE, new CloseOp());
152      inst.put(OP_SET_REPLICATION, new SetReplicationOp());
153      inst.put(OP_CONCAT_DELETE, new ConcatDeleteOp());
154      inst.put(OP_RENAME_OLD, new RenameOldOp());
155      inst.put(OP_DELETE, new DeleteOp());
156      inst.put(OP_MKDIR, new MkdirOp());
157      inst.put(OP_SET_GENSTAMP_V1, new SetGenstampV1Op());
158      inst.put(OP_SET_PERMISSIONS, new SetPermissionsOp());
159      inst.put(OP_SET_OWNER, new SetOwnerOp());
160      inst.put(OP_SET_NS_QUOTA, new SetNSQuotaOp());
161      inst.put(OP_CLEAR_NS_QUOTA, new ClearNSQuotaOp());
162      inst.put(OP_SET_QUOTA, new SetQuotaOp());
163      inst.put(OP_TIMES, new TimesOp());
164      inst.put(OP_SYMLINK, new SymlinkOp());
165      inst.put(OP_RENAME, new RenameOp());
166      inst.put(OP_REASSIGN_LEASE, new ReassignLeaseOp());
167      inst.put(OP_GET_DELEGATION_TOKEN, new GetDelegationTokenOp());
168      inst.put(OP_RENEW_DELEGATION_TOKEN, new RenewDelegationTokenOp());
169      inst.put(OP_CANCEL_DELEGATION_TOKEN, new CancelDelegationTokenOp());
170      inst.put(OP_UPDATE_MASTER_KEY, new UpdateMasterKeyOp());
171      inst.put(OP_START_LOG_SEGMENT, new LogSegmentOp(OP_START_LOG_SEGMENT));
172      inst.put(OP_END_LOG_SEGMENT, new LogSegmentOp(OP_END_LOG_SEGMENT));
173      inst.put(OP_UPDATE_BLOCKS, new UpdateBlocksOp());
174
175      inst.put(OP_ALLOW_SNAPSHOT, new AllowSnapshotOp());
176      inst.put(OP_DISALLOW_SNAPSHOT, new DisallowSnapshotOp());
177      inst.put(OP_CREATE_SNAPSHOT, new CreateSnapshotOp());
178      inst.put(OP_DELETE_SNAPSHOT, new DeleteSnapshotOp());
179      inst.put(OP_RENAME_SNAPSHOT, new RenameSnapshotOp());
180      inst.put(OP_SET_GENSTAMP_V2, new SetGenstampV2Op());
181      inst.put(OP_ALLOCATE_BLOCK_ID, new AllocateBlockIdOp());
182      inst.put(OP_ADD_BLOCK, new AddBlockOp());
183      inst.put(OP_ADD_CACHE_DIRECTIVE,
184          new AddCacheDirectiveInfoOp());
185      inst.put(OP_MODIFY_CACHE_DIRECTIVE,
186          new ModifyCacheDirectiveInfoOp());
187      inst.put(OP_REMOVE_CACHE_DIRECTIVE,
188          new RemoveCacheDirectiveInfoOp());
189      inst.put(OP_ADD_CACHE_POOL, new AddCachePoolOp());
190      inst.put(OP_MODIFY_CACHE_POOL, new ModifyCachePoolOp());
191      inst.put(OP_REMOVE_CACHE_POOL, new RemoveCachePoolOp());
192
193      inst.put(OP_SET_ACL, new SetAclOp());
194      inst.put(OP_ROLLING_UPGRADE_START, new RollingUpgradeOp(
195          OP_ROLLING_UPGRADE_START, "start"));
196      inst.put(OP_ROLLING_UPGRADE_FINALIZE, new RollingUpgradeOp(
197          OP_ROLLING_UPGRADE_FINALIZE, "finalize"));
198      inst.put(OP_SET_XATTR, new SetXAttrOp());
199      inst.put(OP_REMOVE_XATTR, new RemoveXAttrOp());
200      inst.put(OP_SET_STORAGE_POLICY, new SetStoragePolicyOp());
201    }
202    
203    public FSEditLogOp get(FSEditLogOpCodes opcode) {
204      return inst.get(opcode);
205    }
206  }
207
208  private static ImmutableMap<String, FsAction> fsActionMap() {
209    ImmutableMap.Builder<String, FsAction> b = ImmutableMap.builder();
210    for (FsAction v : FsAction.values())
211      b.put(v.SYMBOL, v);
212    return b.build();
213  }
214
215  private static final ImmutableMap<String, FsAction> FSACTION_SYMBOL_MAP
216    = fsActionMap();
217
218  /**
219   * Constructor for an EditLog Op. EditLog ops cannot be constructed
220   * directly, but only through Reader#readOp.
221   */
222  @VisibleForTesting
223  protected FSEditLogOp(FSEditLogOpCodes opCode) {
224    this.opCode = opCode;
225  }
226
227  public long getTransactionId() {
228    Preconditions.checkState(txid != HdfsConstants.INVALID_TXID);
229    return txid;
230  }
231
232  public String getTransactionIdStr() {
233    return (txid == HdfsConstants.INVALID_TXID) ? "(none)" : "" + txid;
234  }
235  
236  public boolean hasTransactionId() {
237    return (txid != HdfsConstants.INVALID_TXID);
238  }
239
240  public void setTransactionId(long txid) {
241    this.txid = txid;
242  }
243  
244  public boolean hasRpcIds() {
245    return rpcClientId != RpcConstants.DUMMY_CLIENT_ID
246        && rpcCallId != RpcConstants.INVALID_CALL_ID;
247  }
248  
249  /** this has to be called after calling {@link #hasRpcIds()} */
250  public byte[] getClientId() {
251    Preconditions.checkState(rpcClientId != RpcConstants.DUMMY_CLIENT_ID);
252    return rpcClientId;
253  }
254  
255  public void setRpcClientId(byte[] clientId) {
256    this.rpcClientId = clientId;
257  }
258  
259  /** this has to be called after calling {@link #hasRpcIds()} */
260  public int getCallId() {
261    Preconditions.checkState(rpcCallId != RpcConstants.INVALID_CALL_ID);
262    return rpcCallId;
263  }
264  
265  public void setRpcCallId(int callId) {
266    this.rpcCallId = callId;
267  }
268
269  abstract void readFields(DataInputStream in, int logVersion)
270      throws IOException;
271
272  public abstract void writeFields(DataOutputStream out)
273      throws IOException;
274
275  static interface BlockListUpdatingOp {
276    Block[] getBlocks();
277    String getPath();
278    boolean shouldCompleteLastBlock();
279  }
280  
281  private static void writeRpcIds(final byte[] clientId, final int callId,
282      DataOutputStream out) throws IOException {
283    FSImageSerialization.writeBytes(clientId, out);
284    FSImageSerialization.writeInt(callId, out);
285  }
286  
287  void readRpcIds(DataInputStream in, int logVersion)
288      throws IOException {
289    if (NameNodeLayoutVersion.supports(
290        LayoutVersion.Feature.EDITLOG_SUPPORT_RETRYCACHE, logVersion)) {
291      this.rpcClientId = FSImageSerialization.readBytes(in);
292      this.rpcCallId = FSImageSerialization.readInt(in);
293    }
294  }
295  
296  void readRpcIdsFromXml(Stanza st) {
297    this.rpcClientId = st.hasChildren("RPC_CLIENTID") ? 
298        ClientId.toBytes(st.getValue("RPC_CLIENTID"))
299        : RpcConstants.DUMMY_CLIENT_ID;
300    this.rpcCallId = st.hasChildren("RPC_CALLID") ? 
301        Integer.parseInt(st.getValue("RPC_CALLID"))
302        : RpcConstants.INVALID_CALL_ID;
303  }
304  
305  private static void appendRpcIdsToString(final StringBuilder builder,
306      final byte[] clientId, final int callId) {
307    builder.append(", RpcClientId=");
308    builder.append(ClientId.toString(clientId));
309    builder.append(", RpcCallId=");
310    builder.append(callId);
311  }
312  
313  private static void appendRpcIdsToXml(ContentHandler contentHandler,
314      final byte[] clientId, final int callId) throws SAXException {
315    XMLUtils.addSaxString(contentHandler, "RPC_CLIENTID",
316        ClientId.toString(clientId));
317    XMLUtils.addSaxString(contentHandler, "RPC_CALLID", 
318        Integer.toString(callId));
319  }
320
321  private static final class AclEditLogUtil {
322    private static final int ACL_EDITLOG_ENTRY_HAS_NAME_OFFSET = 6;
323    private static final int ACL_EDITLOG_ENTRY_TYPE_OFFSET = 3;
324    private static final int ACL_EDITLOG_ENTRY_SCOPE_OFFSET = 5;
325    private static final int ACL_EDITLOG_PERM_MASK = 7;
326    private static final int ACL_EDITLOG_ENTRY_TYPE_MASK = 3;
327    private static final int ACL_EDITLOG_ENTRY_SCOPE_MASK = 1;
328
329    private static final FsAction[] FSACTION_VALUES = FsAction.values();
330    private static final AclEntryScope[] ACL_ENTRY_SCOPE_VALUES = AclEntryScope
331        .values();
332    private static final AclEntryType[] ACL_ENTRY_TYPE_VALUES = AclEntryType
333        .values();
334
335    private static List<AclEntry> read(DataInputStream in, int logVersion)
336        throws IOException {
337      if (!NameNodeLayoutVersion.supports(Feature.EXTENDED_ACL, logVersion)) {
338        return null;
339      }
340
341      int size = in.readInt();
342      if (size == 0) {
343        return null;
344      }
345
346      List<AclEntry> aclEntries = Lists.newArrayListWithCapacity(size);
347      for (int i = 0; i < size; ++i) {
348        int v = in.read();
349        int p = v & ACL_EDITLOG_PERM_MASK;
350        int t = (v >> ACL_EDITLOG_ENTRY_TYPE_OFFSET)
351            & ACL_EDITLOG_ENTRY_TYPE_MASK;
352        int s = (v >> ACL_EDITLOG_ENTRY_SCOPE_OFFSET)
353            & ACL_EDITLOG_ENTRY_SCOPE_MASK;
354        boolean hasName = ((v >> ACL_EDITLOG_ENTRY_HAS_NAME_OFFSET) & 1) == 1;
355        String name = hasName ? FSImageSerialization.readString(in) : null;
356        aclEntries.add(new AclEntry.Builder().setName(name)
357            .setPermission(FSACTION_VALUES[p])
358            .setScope(ACL_ENTRY_SCOPE_VALUES[s])
359            .setType(ACL_ENTRY_TYPE_VALUES[t]).build());
360      }
361
362      return aclEntries;
363    }
364
365    private static void write(List<AclEntry> aclEntries, DataOutputStream out)
366        throws IOException {
367      if (aclEntries == null) {
368        out.writeInt(0);
369        return;
370      }
371
372      out.writeInt(aclEntries.size());
373      for (AclEntry e : aclEntries) {
374        boolean hasName = e.getName() != null;
375        int v = (e.getScope().ordinal() << ACL_EDITLOG_ENTRY_SCOPE_OFFSET)
376            | (e.getType().ordinal() << ACL_EDITLOG_ENTRY_TYPE_OFFSET)
377            | e.getPermission().ordinal();
378
379        if (hasName) {
380          v |= 1 << ACL_EDITLOG_ENTRY_HAS_NAME_OFFSET;
381        }
382        out.write(v);
383        if (hasName) {
384          FSImageSerialization.writeString(e.getName(), out);
385        }
386      }
387    }
388  }
389
390  private static List<XAttr> readXAttrsFromEditLog(DataInputStream in,
391      int logVersion) throws IOException {
392    if (!NameNodeLayoutVersion.supports(NameNodeLayoutVersion.Feature.XATTRS,
393        logVersion)) {
394      return null;
395    }
396    XAttrEditLogProto proto = XAttrEditLogProto.parseDelimitedFrom(in);
397    return PBHelper.convertXAttrs(proto.getXAttrsList());
398  }
399
400  @SuppressWarnings("unchecked")
401  static abstract class AddCloseOp extends FSEditLogOp implements BlockListUpdatingOp {
402    int length;
403    long inodeId;
404    String path;
405    short replication;
406    long mtime;
407    long atime;
408    long blockSize;
409    Block[] blocks;
410    PermissionStatus permissions;
411    List<AclEntry> aclEntries;
412    List<XAttr> xAttrs;
413    String clientName;
414    String clientMachine;
415    boolean overwrite;
416    byte storagePolicyId;
417    
418    private AddCloseOp(FSEditLogOpCodes opCode) {
419      super(opCode);
420      storagePolicyId = BlockStoragePolicySuite.ID_UNSPECIFIED;
421      assert(opCode == OP_ADD || opCode == OP_CLOSE);
422    }
423    
424    <T extends AddCloseOp> T reset() {
425      this.aclEntries = null;
426      this.xAttrs = null;
427      return (T)this;
428    }
429
430    <T extends AddCloseOp> T setInodeId(long inodeId) {
431      this.inodeId = inodeId;
432      return (T)this;
433    }
434
435    <T extends AddCloseOp> T setPath(String path) {
436      this.path = path;
437      return (T)this;
438    }
439    
440    @Override
441    public String getPath() {
442      return path;
443    }
444
445    <T extends AddCloseOp> T setReplication(short replication) {
446      this.replication = replication;
447      return (T)this;
448    }
449
450    <T extends AddCloseOp> T setModificationTime(long mtime) {
451      this.mtime = mtime;
452      return (T)this;
453    }
454
455    <T extends AddCloseOp> T setAccessTime(long atime) {
456      this.atime = atime;
457      return (T)this;
458    }
459
460    <T extends AddCloseOp> T setBlockSize(long blockSize) {
461      this.blockSize = blockSize;
462      return (T)this;
463    }
464
465    <T extends AddCloseOp> T setBlocks(Block[] blocks) {
466      if (blocks.length > MAX_BLOCKS) {
467        throw new RuntimeException("Can't have more than " + MAX_BLOCKS +
468            " in an AddCloseOp.");
469      }
470      this.blocks = blocks;
471      return (T)this;
472    }
473    
474    @Override
475    public Block[] getBlocks() {
476      return blocks;
477    }
478
479    <T extends AddCloseOp> T setPermissionStatus(PermissionStatus permissions) {
480      this.permissions = permissions;
481      return (T)this;
482    }
483
484    <T extends AddCloseOp> T setAclEntries(List<AclEntry> aclEntries) {
485      this.aclEntries = aclEntries;
486      return (T)this;
487    }
488
489    <T extends AddCloseOp> T setXAttrs(List<XAttr> xAttrs) {
490      this.xAttrs = xAttrs;
491      return (T)this;
492    }
493
494    <T extends AddCloseOp> T setClientName(String clientName) {
495      this.clientName = clientName;
496      return (T)this;
497    }
498
499    <T extends AddCloseOp> T setClientMachine(String clientMachine) {
500      this.clientMachine = clientMachine;
501      return (T)this;
502    }
503    
504    <T extends AddCloseOp> T setOverwrite(boolean overwrite) {
505      this.overwrite = overwrite;
506      return (T)this;
507    }
508
509    <T extends AddCloseOp> T setStoragePolicyId(byte storagePolicyId) {
510      this.storagePolicyId = storagePolicyId;
511      return (T)this;
512    }
513
514    @Override
515    public void writeFields(DataOutputStream out) throws IOException {
516      FSImageSerialization.writeLong(inodeId, out);
517      FSImageSerialization.writeString(path, out);
518      FSImageSerialization.writeShort(replication, out);
519      FSImageSerialization.writeLong(mtime, out);
520      FSImageSerialization.writeLong(atime, out);
521      FSImageSerialization.writeLong(blockSize, out);
522      new ArrayWritable(Block.class, blocks).write(out);
523      permissions.write(out);
524
525      if (this.opCode == OP_ADD) {
526        AclEditLogUtil.write(aclEntries, out);
527        XAttrEditLogProto.Builder b = XAttrEditLogProto.newBuilder();
528        b.addAllXAttrs(PBHelper.convertXAttrProto(xAttrs));
529        b.build().writeDelimitedTo(out);
530        FSImageSerialization.writeString(clientName,out);
531        FSImageSerialization.writeString(clientMachine,out);
532        FSImageSerialization.writeBoolean(overwrite, out);
533        FSImageSerialization.writeByte(storagePolicyId, out);
534        // write clientId and callId
535        writeRpcIds(rpcClientId, rpcCallId, out);
536      }
537    }
538
539    @Override
540    void readFields(DataInputStream in, int logVersion)
541        throws IOException {
542      if (!NameNodeLayoutVersion.supports(
543          LayoutVersion.Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
544        this.length = in.readInt();
545      }
546      if (NameNodeLayoutVersion.supports(
547          LayoutVersion.Feature.ADD_INODE_ID, logVersion)) {
548        this.inodeId = in.readLong();
549      } else {
550        // The inodeId should be updated when this editLogOp is applied
551        this.inodeId = INodeId.GRANDFATHER_INODE_ID;
552      }
553      if ((-17 < logVersion && length != 4) ||
554          (logVersion <= -17 && length != 5 && !NameNodeLayoutVersion.supports(
555              LayoutVersion.Feature.EDITLOG_OP_OPTIMIZATION, logVersion))) {
556        throw new IOException("Incorrect data format."  +
557                              " logVersion is " + logVersion +
558                              " but writables.length is " +
559                              length + ". ");
560      }
561      this.path = FSImageSerialization.readString(in);
562
563      if (NameNodeLayoutVersion.supports(
564          LayoutVersion.Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
565        this.replication = FSImageSerialization.readShort(in);
566        this.mtime = FSImageSerialization.readLong(in);
567      } else {
568        this.replication = readShort(in);
569        this.mtime = readLong(in);
570      }
571
572      if (NameNodeLayoutVersion.supports(
573          LayoutVersion.Feature.FILE_ACCESS_TIME, logVersion)) {
574        if (NameNodeLayoutVersion.supports(
575            LayoutVersion.Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
576          this.atime = FSImageSerialization.readLong(in);
577        } else {
578          this.atime = readLong(in);
579        }
580      } else {
581        this.atime = 0;
582      }
583
584      if (NameNodeLayoutVersion.supports(
585          LayoutVersion.Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
586        this.blockSize = FSImageSerialization.readLong(in);
587      } else {
588        this.blockSize = readLong(in);
589      }
590
591      this.blocks = readBlocks(in, logVersion);
592      this.permissions = PermissionStatus.read(in);
593
594      if (this.opCode == OP_ADD) {
595        aclEntries = AclEditLogUtil.read(in, logVersion);
596        this.xAttrs = readXAttrsFromEditLog(in, logVersion);
597        this.clientName = FSImageSerialization.readString(in);
598        this.clientMachine = FSImageSerialization.readString(in);
599        if (NameNodeLayoutVersion.supports(
600            NameNodeLayoutVersion.Feature.CREATE_OVERWRITE, logVersion)) {
601          this.overwrite = FSImageSerialization.readBoolean(in);
602        } else {
603          this.overwrite = false;
604        }
605        if (NameNodeLayoutVersion.supports(
606            NameNodeLayoutVersion.Feature.BLOCK_STORAGE_POLICY, logVersion)) {
607          this.storagePolicyId = FSImageSerialization.readByte(in);
608        } else {
609          this.storagePolicyId = BlockStoragePolicySuite.ID_UNSPECIFIED;
610        }
611        // read clientId and callId
612        readRpcIds(in, logVersion);
613      } else {
614        this.clientName = "";
615        this.clientMachine = "";
616      }
617    }
618
619    static final public int MAX_BLOCKS = 1024 * 1024 * 64;
620    
621    private static Block[] readBlocks(
622        DataInputStream in,
623        int logVersion) throws IOException {
624      int numBlocks = in.readInt();
625      if (numBlocks < 0) {
626        throw new IOException("invalid negative number of blocks");
627      } else if (numBlocks > MAX_BLOCKS) {
628        throw new IOException("invalid number of blocks: " + numBlocks +
629            ".  The maximum number of blocks per file is " + MAX_BLOCKS);
630      }
631      Block[] blocks = new Block[numBlocks];
632      for (int i = 0; i < numBlocks; i++) {
633        Block blk = new Block();
634        blk.readFields(in);
635        blocks[i] = blk;
636      }
637      return blocks;
638    }
639
640    public String stringifyMembers() {
641      StringBuilder builder = new StringBuilder();
642      builder.append("[length=");
643      builder.append(length);
644      builder.append(", inodeId=");
645      builder.append(inodeId);
646      builder.append(", path=");
647      builder.append(path);
648      builder.append(", replication=");
649      builder.append(replication);
650      builder.append(", mtime=");
651      builder.append(mtime);
652      builder.append(", atime=");
653      builder.append(atime);
654      builder.append(", blockSize=");
655      builder.append(blockSize);
656      builder.append(", blocks=");
657      builder.append(Arrays.toString(blocks));
658      builder.append(", permissions=");
659      builder.append(permissions);
660      builder.append(", aclEntries=");
661      builder.append(aclEntries);
662      builder.append(", clientName=");
663      builder.append(clientName);
664      builder.append(", clientMachine=");
665      builder.append(clientMachine);
666      builder.append(", overwrite=");
667      builder.append(overwrite);
668      if (this.opCode == OP_ADD) {
669        appendRpcIdsToString(builder, rpcClientId, rpcCallId);
670      }
671      builder.append(", storagePolicyId=");
672      builder.append(storagePolicyId);
673      builder.append(", opCode=");
674      builder.append(opCode);
675      builder.append(", txid=");
676      builder.append(txid);
677      builder.append("]");
678      return builder.toString();
679    }
680    
681    @Override
682    protected void toXml(ContentHandler contentHandler) throws SAXException {
683      XMLUtils.addSaxString(contentHandler, "LENGTH",
684          Integer.toString(length));
685      XMLUtils.addSaxString(contentHandler, "INODEID",
686          Long.toString(inodeId));
687      XMLUtils.addSaxString(contentHandler, "PATH", path);
688      XMLUtils.addSaxString(contentHandler, "REPLICATION",
689          Short.valueOf(replication).toString());
690      XMLUtils.addSaxString(contentHandler, "MTIME",
691          Long.toString(mtime));
692      XMLUtils.addSaxString(contentHandler, "ATIME",
693          Long.toString(atime));
694      XMLUtils.addSaxString(contentHandler, "BLOCKSIZE",
695          Long.toString(blockSize));
696      XMLUtils.addSaxString(contentHandler, "CLIENT_NAME", clientName);
697      XMLUtils.addSaxString(contentHandler, "CLIENT_MACHINE", clientMachine);
698      XMLUtils.addSaxString(contentHandler, "OVERWRITE", 
699          Boolean.toString(overwrite));
700      for (Block b : blocks) {
701        FSEditLogOp.blockToXml(contentHandler, b);
702      }
703      FSEditLogOp.permissionStatusToXml(contentHandler, permissions);
704      if (this.opCode == OP_ADD) {
705        if (aclEntries != null) {
706          appendAclEntriesToXml(contentHandler, aclEntries);
707        }
708        appendRpcIdsToXml(contentHandler, rpcClientId, rpcCallId);
709      }
710    }
711
712    @Override 
713    void fromXml(Stanza st) throws InvalidXmlException {
714      this.length = Integer.parseInt(st.getValue("LENGTH"));
715      this.inodeId = Long.parseLong(st.getValue("INODEID"));
716      this.path = st.getValue("PATH");
717      this.replication = Short.valueOf(st.getValue("REPLICATION"));
718      this.mtime = Long.parseLong(st.getValue("MTIME"));
719      this.atime = Long.parseLong(st.getValue("ATIME"));
720      this.blockSize = Long.parseLong(st.getValue("BLOCKSIZE"));
721
722      this.clientName = st.getValue("CLIENT_NAME");
723      this.clientMachine = st.getValue("CLIENT_MACHINE");
724      this.overwrite = Boolean.parseBoolean(st.getValueOrNull("OVERWRITE"));
725      if (st.hasChildren("BLOCK")) {
726        List<Stanza> blocks = st.getChildren("BLOCK");
727        this.blocks = new Block[blocks.size()];
728        for (int i = 0; i < blocks.size(); i++) {
729          this.blocks[i] = FSEditLogOp.blockFromXml(blocks.get(i));
730        }
731      } else {
732        this.blocks = new Block[0];
733      }
734      this.permissions = permissionStatusFromXml(st);
735      aclEntries = readAclEntriesFromXml(st);
736      readRpcIdsFromXml(st);
737    }
738  }
739
740  /**
741   * {@literal @AtMostOnce} for {@link ClientProtocol#create} and
742   * {@link ClientProtocol#append}
743   */
744  static class AddOp extends AddCloseOp {
745    private AddOp() {
746      super(OP_ADD);
747    }
748
749    static AddOp getInstance(OpInstanceCache cache) {
750      return (AddOp)cache.get(OP_ADD);
751    }
752
753    @Override
754    public boolean shouldCompleteLastBlock() {
755      return false;
756    }
757
758    @Override
759    public String toString() {
760      StringBuilder builder = new StringBuilder();
761      builder.append("AddOp ");
762      builder.append(stringifyMembers());
763      return builder.toString();
764    }
765  }
766
767  /**
768   * Although {@link ClientProtocol#appendFile} may also log a close op, we do
769   * not need to record the rpc ids here since a successful appendFile op will
770   * finally log an AddOp.
771   */
772  static class CloseOp extends AddCloseOp {
773    private CloseOp() {
774      super(OP_CLOSE);
775    }
776
777    static CloseOp getInstance(OpInstanceCache cache) {
778      return (CloseOp)cache.get(OP_CLOSE);
779    }
780
781    @Override
782    public boolean shouldCompleteLastBlock() {
783      return true;
784    }
785
786    @Override
787    public String toString() {
788      StringBuilder builder = new StringBuilder();
789      builder.append("CloseOp ");
790      builder.append(stringifyMembers());
791      return builder.toString();
792    }
793  }
794  
795  static class AddBlockOp extends FSEditLogOp {
796    private String path;
797    private Block penultimateBlock;
798    private Block lastBlock;
799    
800    private AddBlockOp() {
801      super(OP_ADD_BLOCK);
802    }
803    
804    static AddBlockOp getInstance(OpInstanceCache cache) {
805      return (AddBlockOp) cache.get(OP_ADD_BLOCK);
806    }
807    
808    AddBlockOp setPath(String path) {
809      this.path = path;
810      return this;
811    }
812    
813    public String getPath() {
814      return path;
815    }
816
817    AddBlockOp setPenultimateBlock(Block pBlock) {
818      this.penultimateBlock = pBlock;
819      return this;
820    }
821    
822    Block getPenultimateBlock() {
823      return penultimateBlock;
824    }
825    
826    AddBlockOp setLastBlock(Block lastBlock) {
827      this.lastBlock = lastBlock;
828      return this;
829    }
830    
831    Block getLastBlock() {
832      return lastBlock;
833    }
834
835    @Override
836    public void writeFields(DataOutputStream out) throws IOException {
837      FSImageSerialization.writeString(path, out);
838      int size = penultimateBlock != null ? 2 : 1;
839      Block[] blocks = new Block[size];
840      if (penultimateBlock != null) {
841        blocks[0] = penultimateBlock;
842      }
843      blocks[size - 1] = lastBlock;
844      FSImageSerialization.writeCompactBlockArray(blocks, out);
845      // clientId and callId
846      writeRpcIds(rpcClientId, rpcCallId, out);
847    }
848    
849    @Override
850    void readFields(DataInputStream in, int logVersion) throws IOException {
851      path = FSImageSerialization.readString(in);
852      Block[] blocks = FSImageSerialization.readCompactBlockArray(in,
853          logVersion);
854      Preconditions.checkState(blocks.length == 2 || blocks.length == 1);
855      penultimateBlock = blocks.length == 1 ? null : blocks[0];
856      lastBlock = blocks[blocks.length - 1];
857      readRpcIds(in, logVersion);
858    }
859
860    @Override
861    public String toString() {
862      StringBuilder sb = new StringBuilder();
863      sb.append("AddBlockOp [path=")
864        .append(path)
865        .append(", penultimateBlock=")
866        .append(penultimateBlock == null ? "NULL" : penultimateBlock)
867        .append(", lastBlock=")
868        .append(lastBlock);
869      appendRpcIdsToString(sb, rpcClientId, rpcCallId);
870      sb.append("]");
871      return sb.toString();
872    }
873    
874    @Override
875    protected void toXml(ContentHandler contentHandler) throws SAXException {
876      XMLUtils.addSaxString(contentHandler, "PATH", path);
877      if (penultimateBlock != null) {
878        FSEditLogOp.blockToXml(contentHandler, penultimateBlock);
879      }
880      FSEditLogOp.blockToXml(contentHandler, lastBlock);
881      appendRpcIdsToXml(contentHandler, rpcClientId, rpcCallId);
882    }
883    
884    @Override 
885    void fromXml(Stanza st) throws InvalidXmlException {
886      this.path = st.getValue("PATH");
887      List<Stanza> blocks = st.getChildren("BLOCK");
888      int size = blocks.size();
889      Preconditions.checkState(size == 1 || size == 2);
890      this.penultimateBlock = size == 2 ? 
891          FSEditLogOp.blockFromXml(blocks.get(0)) : null;
892      this.lastBlock = FSEditLogOp.blockFromXml(blocks.get(size - 1));
893      readRpcIdsFromXml(st);
894    }
895  }
896  
897  /**
898   * {@literal @AtMostOnce} for {@link ClientProtocol#updatePipeline}, but 
899   * {@literal @Idempotent} for some other ops.
900   */
901  static class UpdateBlocksOp extends FSEditLogOp implements BlockListUpdatingOp {
902    String path;
903    Block[] blocks;
904    
905    private UpdateBlocksOp() {
906      super(OP_UPDATE_BLOCKS);
907    }
908    
909    static UpdateBlocksOp getInstance(OpInstanceCache cache) {
910      return (UpdateBlocksOp)cache.get(OP_UPDATE_BLOCKS);
911    }
912    
913    UpdateBlocksOp setPath(String path) {
914      this.path = path;
915      return this;
916    }
917    
918    @Override
919    public String getPath() {
920      return path;
921    }
922
923    UpdateBlocksOp setBlocks(Block[] blocks) {
924      this.blocks = blocks;
925      return this;
926    }
927    
928    @Override
929    public Block[] getBlocks() {
930      return blocks;
931    }
932
933    @Override
934    public
935    void writeFields(DataOutputStream out) throws IOException {
936      FSImageSerialization.writeString(path, out);
937      FSImageSerialization.writeCompactBlockArray(blocks, out);
938      // clientId and callId
939      writeRpcIds(rpcClientId, rpcCallId, out);
940    }
941    
942    @Override
943    void readFields(DataInputStream in, int logVersion) throws IOException {
944      path = FSImageSerialization.readString(in);
945      this.blocks = FSImageSerialization.readCompactBlockArray(
946          in, logVersion);
947      readRpcIds(in, logVersion);
948    }
949
950    @Override
951    public boolean shouldCompleteLastBlock() {
952      return false;
953    }
954
955    @Override
956    public String toString() {
957      StringBuilder sb = new StringBuilder();
958      sb.append("UpdateBlocksOp [path=")
959        .append(path)
960        .append(", blocks=")
961        .append(Arrays.toString(blocks));
962      appendRpcIdsToString(sb, rpcClientId, rpcCallId);
963      sb.append("]");
964      return sb.toString();
965    }
966    
967    @Override
968    protected void toXml(ContentHandler contentHandler) throws SAXException {
969      XMLUtils.addSaxString(contentHandler, "PATH", path);
970      for (Block b : blocks) {
971        FSEditLogOp.blockToXml(contentHandler, b);
972      }
973      appendRpcIdsToXml(contentHandler, rpcClientId, rpcCallId);
974    }
975    
976    @Override void fromXml(Stanza st) throws InvalidXmlException {
977      this.path = st.getValue("PATH");
978      List<Stanza> blocks = st.getChildren("BLOCK");
979      this.blocks = new Block[blocks.size()];
980      for (int i = 0; i < blocks.size(); i++) {
981        this.blocks[i] = FSEditLogOp.blockFromXml(blocks.get(i));
982      }
983      readRpcIdsFromXml(st);
984    }
985  }
986
987  /** {@literal @Idempotent} for {@link ClientProtocol#setReplication} */
988  static class SetReplicationOp extends FSEditLogOp {
989    String path;
990    short replication;
991
992    private SetReplicationOp() {
993      super(OP_SET_REPLICATION);
994    }
995
996    static SetReplicationOp getInstance(OpInstanceCache cache) {
997      return (SetReplicationOp)cache.get(OP_SET_REPLICATION);
998    }
999
1000    SetReplicationOp setPath(String path) {
1001      this.path = path;
1002      return this;
1003    }
1004
1005    SetReplicationOp setReplication(short replication) {
1006      this.replication = replication;
1007      return this;
1008    }
1009
1010    @Override
1011    public 
1012    void writeFields(DataOutputStream out) throws IOException {
1013      FSImageSerialization.writeString(path, out);
1014      FSImageSerialization.writeShort(replication, out);
1015    }
1016    
1017    @Override
1018    void readFields(DataInputStream in, int logVersion)
1019        throws IOException {
1020      this.path = FSImageSerialization.readString(in);
1021      if (NameNodeLayoutVersion.supports(
1022          LayoutVersion.Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
1023        this.replication = FSImageSerialization.readShort(in);
1024      } else {
1025        this.replication = readShort(in);
1026      }
1027    }
1028
1029    @Override
1030    public String toString() {
1031      StringBuilder builder = new StringBuilder();
1032      builder.append("SetReplicationOp [path=");
1033      builder.append(path);
1034      builder.append(", replication=");
1035      builder.append(replication);
1036      builder.append(", opCode=");
1037      builder.append(opCode);
1038      builder.append(", txid=");
1039      builder.append(txid);
1040      builder.append("]");
1041      return builder.toString();
1042    }
1043    
1044    @Override
1045    protected void toXml(ContentHandler contentHandler) throws SAXException {
1046      XMLUtils.addSaxString(contentHandler, "PATH", path);
1047      XMLUtils.addSaxString(contentHandler, "REPLICATION",
1048          Short.valueOf(replication).toString());
1049    }
1050    
1051    @Override void fromXml(Stanza st) throws InvalidXmlException {
1052      this.path = st.getValue("PATH");
1053      this.replication = Short.valueOf(st.getValue("REPLICATION"));
1054    }
1055  }
1056
1057  /** {@literal @AtMostOnce} for {@link ClientProtocol#concat} */
1058  static class ConcatDeleteOp extends FSEditLogOp {
1059    int length;
1060    String trg;
1061    String[] srcs;
1062    long timestamp;
1063    final static public int MAX_CONCAT_SRC = 1024 * 1024;
1064
1065    private ConcatDeleteOp() {
1066      super(OP_CONCAT_DELETE);
1067    }
1068
1069    static ConcatDeleteOp getInstance(OpInstanceCache cache) {
1070      return (ConcatDeleteOp)cache.get(OP_CONCAT_DELETE);
1071    }
1072
1073    ConcatDeleteOp setTarget(String trg) {
1074      this.trg = trg;
1075      return this;
1076    }
1077
1078    ConcatDeleteOp setSources(String[] srcs) {
1079      if (srcs.length > MAX_CONCAT_SRC) {
1080        throw new RuntimeException("ConcatDeleteOp can only have " +
1081            MAX_CONCAT_SRC + " sources at most.");
1082      }
1083      this.srcs = srcs;
1084
1085      return this;
1086    }
1087
1088    ConcatDeleteOp setTimestamp(long timestamp) {
1089      this.timestamp = timestamp;
1090      return this;
1091    }
1092
1093    @Override
1094    public void writeFields(DataOutputStream out) throws IOException {
1095      FSImageSerialization.writeString(trg, out);
1096            
1097      DeprecatedUTF8 info[] = new DeprecatedUTF8[srcs.length];
1098      int idx = 0;
1099      for(int i=0; i<srcs.length; i++) {
1100        info[idx++] = new DeprecatedUTF8(srcs[i]);
1101      }
1102      new ArrayWritable(DeprecatedUTF8.class, info).write(out);
1103
1104      FSImageSerialization.writeLong(timestamp, out);
1105      
1106      // rpc ids
1107      writeRpcIds(rpcClientId, rpcCallId, out);
1108    }
1109
1110    @Override
1111    void readFields(DataInputStream in, int logVersion)
1112        throws IOException {
1113      if (!NameNodeLayoutVersion.supports(
1114          LayoutVersion.Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
1115        this.length = in.readInt();
1116        if (length < 3) { // trg, srcs.., timestamp
1117          throw new IOException("Incorrect data format " +
1118              "for ConcatDeleteOp.");
1119        }
1120      }
1121      this.trg = FSImageSerialization.readString(in);
1122      int srcSize = 0;
1123      if (NameNodeLayoutVersion.supports(
1124          LayoutVersion.Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
1125        srcSize = in.readInt();
1126      } else {
1127        srcSize = this.length - 1 - 1; // trg and timestamp
1128      }
1129      if (srcSize < 0) {
1130          throw new IOException("Incorrect data format. "
1131              + "ConcatDeleteOp cannot have a negative number of data " +
1132              " sources.");
1133      } else if (srcSize > MAX_CONCAT_SRC) {
1134          throw new IOException("Incorrect data format. "
1135              + "ConcatDeleteOp can have at most " + MAX_CONCAT_SRC +
1136              " sources, but we tried to have " + (length - 3) + " sources.");
1137      }
1138      this.srcs = new String [srcSize];
1139      for(int i=0; i<srcSize;i++) {
1140        srcs[i]= FSImageSerialization.readString(in);
1141      }
1142      
1143      if (NameNodeLayoutVersion.supports(
1144          LayoutVersion.Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
1145        this.timestamp = FSImageSerialization.readLong(in);
1146      } else {
1147        this.timestamp = readLong(in);
1148      }
1149      // read RPC ids if necessary
1150      readRpcIds(in, logVersion);
1151    }
1152
1153    @Override
1154    public String toString() {
1155      StringBuilder builder = new StringBuilder();
1156      builder.append("ConcatDeleteOp [length=");
1157      builder.append(length);
1158      builder.append(", trg=");
1159      builder.append(trg);
1160      builder.append(", srcs=");
1161      builder.append(Arrays.toString(srcs));
1162      builder.append(", timestamp=");
1163      builder.append(timestamp);
1164      appendRpcIdsToString(builder, rpcClientId, rpcCallId);
1165      builder.append(", opCode=");
1166      builder.append(opCode);
1167      builder.append(", txid=");
1168      builder.append(txid);
1169      builder.append("]");
1170      return builder.toString();
1171    }
1172    
1173    @Override
1174    protected void toXml(ContentHandler contentHandler) throws SAXException {
1175      XMLUtils.addSaxString(contentHandler, "LENGTH",
1176          Integer.toString(length));
1177      XMLUtils.addSaxString(contentHandler, "TRG", trg);
1178      XMLUtils.addSaxString(contentHandler, "TIMESTAMP",
1179          Long.toString(timestamp));
1180      contentHandler.startElement("", "", "SOURCES", new AttributesImpl());
1181      for (int i = 0; i < srcs.length; ++i) {
1182        XMLUtils.addSaxString(contentHandler,
1183            "SOURCE" + (i + 1), srcs[i]);
1184      }
1185      contentHandler.endElement("", "", "SOURCES");
1186      appendRpcIdsToXml(contentHandler, rpcClientId, rpcCallId);
1187    }
1188    
1189    @Override void fromXml(Stanza st) throws InvalidXmlException {
1190      this.length = Integer.parseInt(st.getValue("LENGTH"));
1191      this.trg = st.getValue("TRG");
1192      this.timestamp = Long.parseLong(st.getValue("TIMESTAMP"));
1193      List<Stanza> sources = st.getChildren("SOURCES");
1194      int i = 0;
1195      while (true) {
1196        if (!sources.get(0).hasChildren("SOURCE" + (i + 1)))
1197          break;
1198        i++;
1199      }
1200      srcs = new String[i];
1201      for (i = 0; i < srcs.length; i++) {
1202        srcs[i] = sources.get(0).getValue("SOURCE" + (i + 1));
1203      }
1204      readRpcIdsFromXml(st);
1205    }
1206  }
1207
1208  /** {@literal @AtMostOnce} for {@link ClientProtocol#rename} */
1209  static class RenameOldOp extends FSEditLogOp {
1210    int length;
1211    String src;
1212    String dst;
1213    long timestamp;
1214
1215    private RenameOldOp() {
1216      super(OP_RENAME_OLD);
1217    }
1218
1219    static RenameOldOp getInstance(OpInstanceCache cache) {
1220      return (RenameOldOp)cache.get(OP_RENAME_OLD);
1221    }
1222
1223    RenameOldOp setSource(String src) {
1224      this.src = src;
1225      return this;
1226    }
1227
1228    RenameOldOp setDestination(String dst) {
1229      this.dst = dst;
1230      return this;
1231    }
1232
1233    RenameOldOp setTimestamp(long timestamp) {
1234      this.timestamp = timestamp;
1235      return this;
1236    }
1237
1238    @Override
1239    public 
1240    void writeFields(DataOutputStream out) throws IOException {
1241      FSImageSerialization.writeString(src, out);
1242      FSImageSerialization.writeString(dst, out);
1243      FSImageSerialization.writeLong(timestamp, out);
1244      writeRpcIds(rpcClientId, rpcCallId, out);
1245    }
1246
1247    @Override
1248    void readFields(DataInputStream in, int logVersion)
1249        throws IOException {
1250      if (!NameNodeLayoutVersion.supports(
1251          LayoutVersion.Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
1252        this.length = in.readInt();
1253        if (this.length != 3) {
1254          throw new IOException("Incorrect data format. "
1255              + "Old rename operation.");
1256        }
1257      }
1258      this.src = FSImageSerialization.readString(in);
1259      this.dst = FSImageSerialization.readString(in);
1260      if (NameNodeLayoutVersion.supports(
1261          LayoutVersion.Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
1262        this.timestamp = FSImageSerialization.readLong(in);
1263      } else {
1264        this.timestamp = readLong(in);
1265      }
1266      
1267      // read RPC ids if necessary
1268      readRpcIds(in, logVersion);
1269    }
1270
1271    @Override
1272    public String toString() {
1273      StringBuilder builder = new StringBuilder();
1274      builder.append("RenameOldOp [length=");
1275      builder.append(length);
1276      builder.append(", src=");
1277      builder.append(src);
1278      builder.append(", dst=");
1279      builder.append(dst);
1280      builder.append(", timestamp=");
1281      builder.append(timestamp);
1282      appendRpcIdsToString(builder, rpcClientId, rpcCallId);
1283      builder.append(", opCode=");
1284      builder.append(opCode);
1285      builder.append(", txid=");
1286      builder.append(txid);
1287      builder.append("]");
1288      return builder.toString();
1289    }
1290    
1291    @Override
1292    protected void toXml(ContentHandler contentHandler) throws SAXException {
1293      XMLUtils.addSaxString(contentHandler, "LENGTH",
1294          Integer.toString(length));
1295      XMLUtils.addSaxString(contentHandler, "SRC", src);
1296      XMLUtils.addSaxString(contentHandler, "DST", dst);
1297      XMLUtils.addSaxString(contentHandler, "TIMESTAMP",
1298          Long.toString(timestamp));
1299      appendRpcIdsToXml(contentHandler, rpcClientId, rpcCallId);
1300    }
1301    
1302    @Override 
1303    void fromXml(Stanza st) throws InvalidXmlException {
1304      this.length = Integer.parseInt(st.getValue("LENGTH"));
1305      this.src = st.getValue("SRC");
1306      this.dst = st.getValue("DST");
1307      this.timestamp = Long.parseLong(st.getValue("TIMESTAMP"));
1308      
1309      readRpcIdsFromXml(st);
1310    }
1311  }
1312
1313  /** {@literal @AtMostOnce} for {@link ClientProtocol#delete} */
1314  static class DeleteOp extends FSEditLogOp {
1315    int length;
1316    String path;
1317    long timestamp;
1318
1319    private DeleteOp() {
1320      super(OP_DELETE);
1321    }
1322
1323    static DeleteOp getInstance(OpInstanceCache cache) {
1324      return (DeleteOp)cache.get(OP_DELETE);
1325    }
1326
1327    DeleteOp setPath(String path) {
1328      this.path = path;
1329      return this;
1330    }
1331
1332    DeleteOp setTimestamp(long timestamp) {
1333      this.timestamp = timestamp;
1334      return this;
1335    }
1336
1337    @Override
1338    public 
1339    void writeFields(DataOutputStream out) throws IOException {
1340      FSImageSerialization.writeString(path, out);
1341      FSImageSerialization.writeLong(timestamp, out);
1342      writeRpcIds(rpcClientId, rpcCallId, out);
1343    }
1344
1345    @Override
1346    void readFields(DataInputStream in, int logVersion)
1347        throws IOException {
1348      if (!NameNodeLayoutVersion.supports(
1349          LayoutVersion.Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
1350        this.length = in.readInt();
1351        if (this.length != 2) {
1352          throw new IOException("Incorrect data format. " + "delete operation.");
1353        }
1354      }
1355      this.path = FSImageSerialization.readString(in);
1356      if (NameNodeLayoutVersion.supports(
1357          LayoutVersion.Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
1358        this.timestamp = FSImageSerialization.readLong(in);
1359      } else {
1360        this.timestamp = readLong(in);
1361      }
1362      // read RPC ids if necessary
1363      readRpcIds(in, logVersion);
1364    }
1365
1366    @Override
1367    public String toString() {
1368      StringBuilder builder = new StringBuilder();
1369      builder.append("DeleteOp [length=");
1370      builder.append(length);
1371      builder.append(", path=");
1372      builder.append(path);
1373      builder.append(", timestamp=");
1374      builder.append(timestamp);
1375      appendRpcIdsToString(builder, rpcClientId, rpcCallId);
1376      builder.append(", opCode=");
1377      builder.append(opCode);
1378      builder.append(", txid=");
1379      builder.append(txid);
1380      builder.append("]");
1381      return builder.toString();
1382    }
1383    
1384    @Override
1385    protected void toXml(ContentHandler contentHandler) throws SAXException {
1386      XMLUtils.addSaxString(contentHandler, "LENGTH",
1387          Integer.toString(length));
1388      XMLUtils.addSaxString(contentHandler, "PATH", path);
1389      XMLUtils.addSaxString(contentHandler, "TIMESTAMP",
1390          Long.toString(timestamp));
1391      appendRpcIdsToXml(contentHandler, rpcClientId, rpcCallId);
1392    }
1393    
1394    @Override void fromXml(Stanza st) throws InvalidXmlException {
1395      this.length = Integer.parseInt(st.getValue("LENGTH"));
1396      this.path = st.getValue("PATH");
1397      this.timestamp = Long.parseLong(st.getValue("TIMESTAMP"));
1398      
1399      readRpcIdsFromXml(st);
1400    }
1401  }
1402
1403  /** {@literal @Idempotent} for {@link ClientProtocol#mkdirs} */
1404  static class MkdirOp extends FSEditLogOp {
1405    int length;
1406    long inodeId;
1407    String path;
1408    long timestamp;
1409    PermissionStatus permissions;
1410    List<AclEntry> aclEntries;
1411    List<XAttr> xAttrs;
1412
1413    private MkdirOp() {
1414      super(OP_MKDIR);
1415    }
1416    
1417    static MkdirOp getInstance(OpInstanceCache cache) {
1418      return (MkdirOp)cache.get(OP_MKDIR);
1419    }
1420
1421    MkdirOp reset() {
1422      this.aclEntries = null;
1423      this.xAttrs = null;
1424      return this;
1425    }
1426
1427    MkdirOp setInodeId(long inodeId) {
1428      this.inodeId = inodeId;
1429      return this;
1430    }
1431    
1432    MkdirOp setPath(String path) {
1433      this.path = path;
1434      return this;
1435    }
1436
1437    MkdirOp setTimestamp(long timestamp) {
1438      this.timestamp = timestamp;
1439      return this;
1440    }
1441
1442    MkdirOp setPermissionStatus(PermissionStatus permissions) {
1443      this.permissions = permissions;
1444      return this;
1445    }
1446
1447    MkdirOp setAclEntries(List<AclEntry> aclEntries) {
1448      this.aclEntries = aclEntries;
1449      return this;
1450    }
1451
1452    MkdirOp setXAttrs(List<XAttr> xAttrs) {
1453      this.xAttrs = xAttrs;
1454      return this;
1455    }
1456
1457    @Override
1458    public 
1459    void writeFields(DataOutputStream out) throws IOException {
1460      FSImageSerialization.writeLong(inodeId, out);
1461      FSImageSerialization.writeString(path, out);
1462      FSImageSerialization.writeLong(timestamp, out); // mtime
1463      FSImageSerialization.writeLong(timestamp, out); // atime, unused at this
1464      permissions.write(out);
1465      AclEditLogUtil.write(aclEntries, out);
1466      XAttrEditLogProto.Builder b = XAttrEditLogProto.newBuilder();
1467      b.addAllXAttrs(PBHelper.convertXAttrProto(xAttrs));
1468      b.build().writeDelimitedTo(out);
1469    }
1470    
1471    @Override
1472    void readFields(DataInputStream in, int logVersion) throws IOException {
1473      if (!NameNodeLayoutVersion.supports(
1474          LayoutVersion.Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
1475        this.length = in.readInt();
1476      }
1477      if (-17 < logVersion && length != 2 ||
1478          logVersion <= -17 && length != 3
1479          && !NameNodeLayoutVersion.supports(
1480              LayoutVersion.Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
1481        throw new IOException("Incorrect data format. Mkdir operation.");
1482      }
1483      if (NameNodeLayoutVersion.supports(
1484          LayoutVersion.Feature.ADD_INODE_ID, logVersion)) {
1485        this.inodeId = FSImageSerialization.readLong(in);
1486      } else {
1487        // This id should be updated when this editLogOp is applied
1488        this.inodeId = INodeId.GRANDFATHER_INODE_ID;
1489      }
1490      this.path = FSImageSerialization.readString(in);
1491      if (NameNodeLayoutVersion.supports(
1492          LayoutVersion.Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
1493        this.timestamp = FSImageSerialization.readLong(in);
1494      } else {
1495        this.timestamp = readLong(in);
1496      }
1497
1498      // The disk format stores atimes for directories as well.
1499      // However, currently this is not being updated/used because of
1500      // performance reasons.
1501      if (NameNodeLayoutVersion.supports(
1502          LayoutVersion.Feature.FILE_ACCESS_TIME, logVersion)) {
1503        if (NameNodeLayoutVersion.supports(
1504            LayoutVersion.Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
1505          FSImageSerialization.readLong(in);
1506        } else {
1507          readLong(in);
1508        }
1509      }
1510
1511      this.permissions = PermissionStatus.read(in);
1512      aclEntries = AclEditLogUtil.read(in, logVersion);
1513
1514      xAttrs = readXAttrsFromEditLog(in, logVersion);
1515    }
1516
1517    @Override
1518    public String toString() {
1519      StringBuilder builder = new StringBuilder();
1520      builder.append("MkdirOp [length=");
1521      builder.append(length);
1522      builder.append(", inodeId=");
1523      builder.append(inodeId);
1524      builder.append(", path=");
1525      builder.append(path);
1526      builder.append(", timestamp=");
1527      builder.append(timestamp);
1528      builder.append(", permissions=");
1529      builder.append(permissions);
1530      builder.append(", aclEntries=");
1531      builder.append(aclEntries);
1532      builder.append(", opCode=");
1533      builder.append(opCode);
1534      builder.append(", txid=");
1535      builder.append(txid);
1536      builder.append(", xAttrs=");
1537      builder.append(xAttrs);
1538      builder.append("]");
1539      return builder.toString();
1540    }
1541
1542    @Override
1543    protected void toXml(ContentHandler contentHandler) throws SAXException {
1544      XMLUtils.addSaxString(contentHandler, "LENGTH",
1545          Integer.toString(length));
1546      XMLUtils.addSaxString(contentHandler, "INODEID",
1547          Long.toString(inodeId));
1548      XMLUtils.addSaxString(contentHandler, "PATH", path);
1549      XMLUtils.addSaxString(contentHandler, "TIMESTAMP",
1550          Long.toString(timestamp));
1551      FSEditLogOp.permissionStatusToXml(contentHandler, permissions);
1552      if (aclEntries != null) {
1553        appendAclEntriesToXml(contentHandler, aclEntries);
1554      }
1555      if (xAttrs != null) {
1556        appendXAttrsToXml(contentHandler, xAttrs);
1557      }
1558    }
1559    
1560    @Override void fromXml(Stanza st) throws InvalidXmlException {
1561      this.length = Integer.parseInt(st.getValue("LENGTH"));
1562      this.inodeId = Long.parseLong(st.getValue("INODEID"));
1563      this.path = st.getValue("PATH");
1564      this.timestamp = Long.parseLong(st.getValue("TIMESTAMP"));
1565      this.permissions = permissionStatusFromXml(st);
1566      aclEntries = readAclEntriesFromXml(st);
1567      xAttrs = readXAttrsFromXml(st);
1568    }
1569  }
1570
1571  /**
1572   * The corresponding operations are either {@literal @Idempotent} (
1573   * {@link ClientProtocol#updateBlockForPipeline},
1574   * {@link ClientProtocol#recoverLease}, {@link ClientProtocol#addBlock}) or
1575   * already bound with other editlog op which records rpc ids (
1576   * {@link ClientProtocol#startFile}). Thus no need to record rpc ids here.
1577   */
1578  static class SetGenstampV1Op extends FSEditLogOp {
1579    long genStampV1;
1580
1581    private SetGenstampV1Op() {
1582      super(OP_SET_GENSTAMP_V1);
1583    }
1584
1585    static SetGenstampV1Op getInstance(OpInstanceCache cache) {
1586      return (SetGenstampV1Op)cache.get(OP_SET_GENSTAMP_V1);
1587    }
1588
1589    SetGenstampV1Op setGenerationStamp(long genStamp) {
1590      this.genStampV1 = genStamp;
1591      return this;
1592    }
1593
1594    @Override
1595    public
1596    void writeFields(DataOutputStream out) throws IOException {
1597      FSImageSerialization.writeLong(genStampV1, out);
1598    }
1599
1600    @Override
1601    void readFields(DataInputStream in, int logVersion)
1602        throws IOException {
1603      this.genStampV1 = FSImageSerialization.readLong(in);
1604    }
1605
1606    @Override
1607    public String toString() {
1608      StringBuilder builder = new StringBuilder();
1609      builder.append("SetGenstampOp [GenStamp=");
1610      builder.append(genStampV1);
1611      builder.append(", opCode=");
1612      builder.append(opCode);
1613      builder.append(", txid=");
1614      builder.append(txid);
1615      builder.append("]");
1616      return builder.toString();
1617    }
1618
1619    @Override
1620    protected void toXml(ContentHandler contentHandler) throws SAXException {
1621      XMLUtils.addSaxString(contentHandler, "GENSTAMP",
1622                            Long.toString(genStampV1));
1623    }
1624
1625    @Override void fromXml(Stanza st) throws InvalidXmlException {
1626      this.genStampV1 = Long.parseLong(st.getValue("GENSTAMP"));
1627    }
1628  }
1629
1630  /** Similar with {@link SetGenstampV1Op} */
1631  static class SetGenstampV2Op extends FSEditLogOp {
1632    long genStampV2;
1633
1634    private SetGenstampV2Op() {
1635      super(OP_SET_GENSTAMP_V2);
1636    }
1637
1638    static SetGenstampV2Op getInstance(OpInstanceCache cache) {
1639      return (SetGenstampV2Op)cache.get(OP_SET_GENSTAMP_V2);
1640    }
1641
1642    SetGenstampV2Op setGenerationStamp(long genStamp) {
1643      this.genStampV2 = genStamp;
1644      return this;
1645    }
1646
1647    @Override
1648    public
1649    void writeFields(DataOutputStream out) throws IOException {
1650      FSImageSerialization.writeLong(genStampV2, out);
1651    }
1652
1653    @Override
1654    void readFields(DataInputStream in, int logVersion)
1655        throws IOException {
1656      this.genStampV2 = FSImageSerialization.readLong(in);
1657    }
1658
1659    @Override
1660    public String toString() {
1661      StringBuilder builder = new StringBuilder();
1662      builder.append("SetGenstampV2Op [GenStampV2=");
1663      builder.append(genStampV2);
1664      builder.append(", opCode=");
1665      builder.append(opCode);
1666      builder.append(", txid=");
1667      builder.append(txid);
1668      builder.append("]");
1669      return builder.toString();
1670    }
1671
1672    @Override
1673    protected void toXml(ContentHandler contentHandler) throws SAXException {
1674      XMLUtils.addSaxString(contentHandler, "GENSTAMPV2",
1675                            Long.toString(genStampV2));
1676    }
1677
1678    @Override void fromXml(Stanza st) throws InvalidXmlException {
1679      this.genStampV2 = Long.parseLong(st.getValue("GENSTAMPV2"));
1680    }
1681  }
1682
1683  /** {@literal @Idempotent} for {@link ClientProtocol#addBlock} */
1684  static class AllocateBlockIdOp extends FSEditLogOp {
1685    long blockId;
1686
1687    private AllocateBlockIdOp() {
1688      super(OP_ALLOCATE_BLOCK_ID);
1689    }
1690
1691    static AllocateBlockIdOp getInstance(OpInstanceCache cache) {
1692      return (AllocateBlockIdOp)cache.get(OP_ALLOCATE_BLOCK_ID);
1693    }
1694
1695    AllocateBlockIdOp setBlockId(long blockId) {
1696      this.blockId = blockId;
1697      return this;
1698    }
1699
1700    @Override
1701    public
1702    void writeFields(DataOutputStream out) throws IOException {
1703      FSImageSerialization.writeLong(blockId, out);
1704    }
1705
1706    @Override
1707    void readFields(DataInputStream in, int logVersion)
1708        throws IOException {
1709      this.blockId = FSImageSerialization.readLong(in);
1710    }
1711
1712    @Override
1713    public String toString() {
1714      StringBuilder builder = new StringBuilder();
1715      builder.append("AllocateBlockIdOp [blockId=");
1716      builder.append(blockId);
1717      builder.append(", opCode=");
1718      builder.append(opCode);
1719      builder.append(", txid=");
1720      builder.append(txid);
1721      builder.append("]");
1722      return builder.toString();
1723    }
1724
1725    @Override
1726    protected void toXml(ContentHandler contentHandler) throws SAXException {
1727      XMLUtils.addSaxString(contentHandler, "BLOCK_ID",
1728                            Long.toString(blockId));
1729    }
1730
1731    @Override void fromXml(Stanza st) throws InvalidXmlException {
1732      this.blockId = Long.parseLong(st.getValue("BLOCK_ID"));
1733    }
1734  }
1735
1736  /** {@literal @Idempotent} for {@link ClientProtocol#setPermission} */
1737  static class SetPermissionsOp extends FSEditLogOp {
1738    String src;
1739    FsPermission permissions;
1740
1741    private SetPermissionsOp() {
1742      super(OP_SET_PERMISSIONS);
1743    }
1744
1745    static SetPermissionsOp getInstance(OpInstanceCache cache) {
1746      return (SetPermissionsOp)cache.get(OP_SET_PERMISSIONS);
1747    }
1748
1749    SetPermissionsOp setSource(String src) {
1750      this.src = src;
1751      return this;
1752    }
1753
1754    SetPermissionsOp setPermissions(FsPermission permissions) {
1755      this.permissions = permissions;
1756      return this;
1757    }
1758
1759    @Override
1760    public 
1761    void writeFields(DataOutputStream out) throws IOException {
1762      FSImageSerialization.writeString(src, out);
1763      permissions.write(out);
1764     }
1765 
1766    @Override
1767    void readFields(DataInputStream in, int logVersion)
1768        throws IOException {
1769      this.src = FSImageSerialization.readString(in);
1770      this.permissions = FsPermission.read(in);
1771    }
1772
1773    @Override
1774    public String toString() {
1775      StringBuilder builder = new StringBuilder();
1776      builder.append("SetPermissionsOp [src=");
1777      builder.append(src);
1778      builder.append(", permissions=");
1779      builder.append(permissions);
1780      builder.append(", opCode=");
1781      builder.append(opCode);
1782      builder.append(", txid=");
1783      builder.append(txid);
1784      builder.append("]");
1785      return builder.toString();
1786    }
1787    
1788    @Override
1789    protected void toXml(ContentHandler contentHandler) throws SAXException {
1790      XMLUtils.addSaxString(contentHandler, "SRC", src);
1791      XMLUtils.addSaxString(contentHandler, "MODE",
1792          Short.valueOf(permissions.toShort()).toString());
1793    }
1794    
1795    @Override void fromXml(Stanza st) throws InvalidXmlException {
1796      this.src = st.getValue("SRC");
1797      this.permissions = new FsPermission(
1798          Short.valueOf(st.getValue("MODE")));
1799    }
1800  }
1801
1802  /** {@literal @Idempotent} for {@link ClientProtocol#setOwner} */
1803  static class SetOwnerOp extends FSEditLogOp {
1804    String src;
1805    String username;
1806    String groupname;
1807
1808    private SetOwnerOp() {
1809      super(OP_SET_OWNER);
1810    }
1811
1812    static SetOwnerOp getInstance(OpInstanceCache cache) {
1813      return (SetOwnerOp)cache.get(OP_SET_OWNER);
1814    }
1815
1816    SetOwnerOp setSource(String src) {
1817      this.src = src;
1818      return this;
1819    }
1820
1821    SetOwnerOp setUser(String username) {
1822      this.username = username;
1823      return this;
1824    }
1825
1826    SetOwnerOp setGroup(String groupname) {
1827      this.groupname = groupname;
1828      return this;
1829    }
1830
1831    @Override
1832    public 
1833    void writeFields(DataOutputStream out) throws IOException {
1834      FSImageSerialization.writeString(src, out);
1835      FSImageSerialization.writeString(username == null ? "" : username, out);
1836      FSImageSerialization.writeString(groupname == null ? "" : groupname, out);
1837    }
1838
1839    @Override
1840    void readFields(DataInputStream in, int logVersion)
1841        throws IOException {
1842      this.src = FSImageSerialization.readString(in);
1843      this.username = FSImageSerialization.readString_EmptyAsNull(in);
1844      this.groupname = FSImageSerialization.readString_EmptyAsNull(in);
1845    }
1846
1847    @Override
1848    public String toString() {
1849      StringBuilder builder = new StringBuilder();
1850      builder.append("SetOwnerOp [src=");
1851      builder.append(src);
1852      builder.append(", username=");
1853      builder.append(username);
1854      builder.append(", groupname=");
1855      builder.append(groupname);
1856      builder.append(", opCode=");
1857      builder.append(opCode);
1858      builder.append(", txid=");
1859      builder.append(txid);
1860      builder.append("]");
1861      return builder.toString();
1862    }
1863    
1864    @Override
1865    protected void toXml(ContentHandler contentHandler) throws SAXException {
1866      XMLUtils.addSaxString(contentHandler, "SRC", src);
1867      if (username != null) {
1868        XMLUtils.addSaxString(contentHandler, "USERNAME", username);
1869      }
1870      if (groupname != null) {
1871        XMLUtils.addSaxString(contentHandler, "GROUPNAME", groupname);
1872      }
1873    }
1874    
1875    @Override void fromXml(Stanza st) throws InvalidXmlException {
1876      this.src = st.getValue("SRC");
1877      this.username = (st.hasChildren("USERNAME")) ? 
1878          st.getValue("USERNAME") : null;
1879      this.groupname = (st.hasChildren("GROUPNAME")) ? 
1880          st.getValue("GROUPNAME") : null;
1881    }
1882  }
1883  
1884  static class SetNSQuotaOp extends FSEditLogOp {
1885    String src;
1886    long nsQuota;
1887
1888    private SetNSQuotaOp() {
1889      super(OP_SET_NS_QUOTA);
1890    }
1891
1892    static SetNSQuotaOp getInstance(OpInstanceCache cache) {
1893      return (SetNSQuotaOp)cache.get(OP_SET_NS_QUOTA);
1894    }
1895
1896    @Override
1897    public 
1898    void writeFields(DataOutputStream out) throws IOException {
1899      throw new IOException("Deprecated");      
1900    }
1901
1902    @Override
1903    void readFields(DataInputStream in, int logVersion)
1904        throws IOException {
1905      this.src = FSImageSerialization.readString(in);
1906      this.nsQuota = FSImageSerialization.readLong(in);
1907    }
1908
1909    @Override
1910    public String toString() {
1911      StringBuilder builder = new StringBuilder();
1912      builder.append("SetNSQuotaOp [src=");
1913      builder.append(src);
1914      builder.append(", nsQuota=");
1915      builder.append(nsQuota);
1916      builder.append(", opCode=");
1917      builder.append(opCode);
1918      builder.append(", txid=");
1919      builder.append(txid);
1920      builder.append("]");
1921      return builder.toString();
1922    }
1923    
1924    @Override
1925    protected void toXml(ContentHandler contentHandler) throws SAXException {
1926      XMLUtils.addSaxString(contentHandler, "SRC", src);
1927      XMLUtils.addSaxString(contentHandler, "NSQUOTA",
1928          Long.toString(nsQuota));
1929    }
1930    
1931    @Override void fromXml(Stanza st) throws InvalidXmlException {
1932      this.src = st.getValue("SRC");
1933      this.nsQuota = Long.parseLong(st.getValue("NSQUOTA"));
1934    }
1935  }
1936
1937  static class ClearNSQuotaOp extends FSEditLogOp {
1938    String src;
1939
1940    private ClearNSQuotaOp() {
1941      super(OP_CLEAR_NS_QUOTA);
1942    }
1943
1944    static ClearNSQuotaOp getInstance(OpInstanceCache cache) {
1945      return (ClearNSQuotaOp)cache.get(OP_CLEAR_NS_QUOTA);
1946    }
1947
1948    @Override
1949    public 
1950    void writeFields(DataOutputStream out) throws IOException {
1951      throw new IOException("Deprecated");      
1952    }
1953
1954    @Override
1955    void readFields(DataInputStream in, int logVersion)
1956        throws IOException {
1957      this.src = FSImageSerialization.readString(in);
1958    }
1959
1960    @Override
1961    public String toString() {
1962      StringBuilder builder = new StringBuilder();
1963      builder.append("ClearNSQuotaOp [src=");
1964      builder.append(src);
1965      builder.append(", opCode=");
1966      builder.append(opCode);
1967      builder.append(", txid=");
1968      builder.append(txid);
1969      builder.append("]");
1970      return builder.toString();
1971    }
1972    
1973    @Override
1974    protected void toXml(ContentHandler contentHandler) throws SAXException {
1975      XMLUtils.addSaxString(contentHandler, "SRC", src);
1976    }
1977    
1978    @Override void fromXml(Stanza st) throws InvalidXmlException {
1979      this.src = st.getValue("SRC");
1980    }
1981  }
1982
1983  /** {@literal @Idempotent} for {@link ClientProtocol#setQuota} */
1984  static class SetQuotaOp extends FSEditLogOp {
1985    String src;
1986    long nsQuota;
1987    long dsQuota;
1988
1989    private SetQuotaOp() {
1990      super(OP_SET_QUOTA);
1991    }
1992
1993    static SetQuotaOp getInstance(OpInstanceCache cache) {
1994      return (SetQuotaOp)cache.get(OP_SET_QUOTA);
1995    }
1996
1997    SetQuotaOp setSource(String src) {
1998      this.src = src;
1999      return this;
2000    }
2001
2002    SetQuotaOp setNSQuota(long nsQuota) {
2003      this.nsQuota = nsQuota;
2004      return this;
2005    }
2006
2007    SetQuotaOp setDSQuota(long dsQuota) {
2008      this.dsQuota = dsQuota;
2009      return this;
2010    }
2011
2012    @Override
2013    public 
2014    void writeFields(DataOutputStream out) throws IOException {
2015      FSImageSerialization.writeString(src, out);
2016      FSImageSerialization.writeLong(nsQuota, out);
2017      FSImageSerialization.writeLong(dsQuota, out);
2018    }
2019
2020    @Override
2021    void readFields(DataInputStream in, int logVersion)
2022        throws IOException {
2023      this.src = FSImageSerialization.readString(in);
2024      this.nsQuota = FSImageSerialization.readLong(in);
2025      this.dsQuota = FSImageSerialization.readLong(in);
2026    }
2027
2028    @Override
2029    public String toString() {
2030      StringBuilder builder = new StringBuilder();
2031      builder.append("SetQuotaOp [src=");
2032      builder.append(src);
2033      builder.append(", nsQuota=");
2034      builder.append(nsQuota);
2035      builder.append(", dsQuota=");
2036      builder.append(dsQuota);
2037      builder.append(", opCode=");
2038      builder.append(opCode);
2039      builder.append(", txid=");
2040      builder.append(txid);
2041      builder.append("]");
2042      return builder.toString();
2043    }
2044    
2045    @Override
2046    protected void toXml(ContentHandler contentHandler) throws SAXException {
2047      XMLUtils.addSaxString(contentHandler, "SRC", src);
2048      XMLUtils.addSaxString(contentHandler, "NSQUOTA",
2049          Long.toString(nsQuota));
2050      XMLUtils.addSaxString(contentHandler, "DSQUOTA",
2051          Long.toString(dsQuota));
2052    }
2053    
2054    @Override void fromXml(Stanza st) throws InvalidXmlException {
2055      this.src = st.getValue("SRC");
2056      this.nsQuota = Long.parseLong(st.getValue("NSQUOTA"));
2057      this.dsQuota = Long.parseLong(st.getValue("DSQUOTA"));
2058    }
2059  }
2060
2061  /** {@literal @Idempotent} for {@link ClientProtocol#setTimes} */
2062  static class TimesOp extends FSEditLogOp {
2063    int length;
2064    String path;
2065    long mtime;
2066    long atime;
2067
2068    private TimesOp() {
2069      super(OP_TIMES);
2070    }
2071
2072    static TimesOp getInstance(OpInstanceCache cache) {
2073      return (TimesOp)cache.get(OP_TIMES);
2074    }
2075
2076    TimesOp setPath(String path) {
2077      this.path = path;
2078      return this;
2079    }
2080
2081    TimesOp setModificationTime(long mtime) {
2082      this.mtime = mtime;
2083      return this;
2084    }
2085
2086    TimesOp setAccessTime(long atime) {
2087      this.atime = atime;
2088      return this;
2089    }
2090
2091    @Override
2092    public 
2093    void writeFields(DataOutputStream out) throws IOException {
2094      FSImageSerialization.writeString(path, out);
2095      FSImageSerialization.writeLong(mtime, out);
2096      FSImageSerialization.writeLong(atime, out);
2097    }
2098
2099    @Override
2100    void readFields(DataInputStream in, int logVersion)
2101        throws IOException {
2102      if (!NameNodeLayoutVersion.supports(
2103          LayoutVersion.Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
2104        this.length = in.readInt();
2105        if (length != 3) {
2106          throw new IOException("Incorrect data format. " + "times operation.");
2107        }
2108      }
2109      this.path = FSImageSerialization.readString(in);
2110
2111      if (NameNodeLayoutVersion.supports(
2112          LayoutVersion.Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
2113        this.mtime = FSImageSerialization.readLong(in);
2114        this.atime = FSImageSerialization.readLong(in);
2115      } else {
2116        this.mtime = readLong(in);
2117        this.atime = readLong(in);
2118      }
2119    }
2120
2121    @Override
2122    public String toString() {
2123      StringBuilder builder = new StringBuilder();
2124      builder.append("TimesOp [length=");
2125      builder.append(length);
2126      builder.append(", path=");
2127      builder.append(path);
2128      builder.append(", mtime=");
2129      builder.append(mtime);
2130      builder.append(", atime=");
2131      builder.append(atime);
2132      builder.append(", opCode=");
2133      builder.append(opCode);
2134      builder.append(", txid=");
2135      builder.append(txid);
2136      builder.append("]");
2137      return builder.toString();
2138    }
2139    
2140    @Override
2141    protected void toXml(ContentHandler contentHandler) throws SAXException {
2142      XMLUtils.addSaxString(contentHandler, "LENGTH",
2143          Integer.toString(length));
2144      XMLUtils.addSaxString(contentHandler, "PATH", path);
2145      XMLUtils.addSaxString(contentHandler, "MTIME",
2146          Long.toString(mtime));
2147      XMLUtils.addSaxString(contentHandler, "ATIME",
2148          Long.toString(atime));
2149    }
2150    
2151    @Override void fromXml(Stanza st) throws InvalidXmlException {
2152      this.length = Integer.parseInt(st.getValue("LENGTH"));
2153      this.path = st.getValue("PATH");
2154      this.mtime = Long.parseLong(st.getValue("MTIME"));
2155      this.atime = Long.parseLong(st.getValue("ATIME"));
2156    }
2157  }
2158
2159  /** {@literal @AtMostOnce} for {@link ClientProtocol#createSymlink} */
2160  static class SymlinkOp extends FSEditLogOp {
2161    int length;
2162    long inodeId;
2163    String path;
2164    String value;
2165    long mtime;
2166    long atime;
2167    PermissionStatus permissionStatus;
2168
2169    private SymlinkOp() {
2170      super(OP_SYMLINK);
2171    }
2172
2173    static SymlinkOp getInstance(OpInstanceCache cache) {
2174      return (SymlinkOp)cache.get(OP_SYMLINK);
2175    }
2176
2177    SymlinkOp setId(long inodeId) {
2178      this.inodeId = inodeId;
2179      return this;
2180    }
2181    
2182    SymlinkOp setPath(String path) {
2183      this.path = path;
2184      return this;
2185    }
2186
2187    SymlinkOp setValue(String value) {
2188      this.value = value;
2189      return this;
2190    }
2191
2192    SymlinkOp setModificationTime(long mtime) {
2193      this.mtime = mtime;
2194      return this;
2195    }
2196
2197    SymlinkOp setAccessTime(long atime) {
2198      this.atime = atime;
2199      return this;
2200    }
2201
2202    SymlinkOp setPermissionStatus(PermissionStatus permissionStatus) {
2203      this.permissionStatus = permissionStatus;
2204      return this;
2205    }
2206
2207    @Override
2208    public void writeFields(DataOutputStream out) throws IOException {
2209      FSImageSerialization.writeLong(inodeId, out);      
2210      FSImageSerialization.writeString(path, out);
2211      FSImageSerialization.writeString(value, out);
2212      FSImageSerialization.writeLong(mtime, out);
2213      FSImageSerialization.writeLong(atime, out);
2214      permissionStatus.write(out);
2215      writeRpcIds(rpcClientId, rpcCallId, out);
2216    }
2217
2218    @Override
2219    void readFields(DataInputStream in, int logVersion)
2220        throws IOException {
2221      if (!NameNodeLayoutVersion.supports(
2222          LayoutVersion.Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
2223        this.length = in.readInt();
2224        if (this.length != 4) {
2225          throw new IOException("Incorrect data format. "
2226              + "symlink operation.");
2227        }
2228      }
2229      if (NameNodeLayoutVersion.supports(
2230          LayoutVersion.Feature.ADD_INODE_ID, logVersion)) {
2231        this.inodeId = FSImageSerialization.readLong(in);
2232      } else {
2233        // This id should be updated when the editLogOp is applied
2234        this.inodeId = INodeId.GRANDFATHER_INODE_ID;
2235      }
2236      this.path = FSImageSerialization.readString(in);
2237      this.value = FSImageSerialization.readString(in);
2238
2239      if (NameNodeLayoutVersion.supports(
2240          LayoutVersion.Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
2241        this.mtime = FSImageSerialization.readLong(in);
2242        this.atime = FSImageSerialization.readLong(in);
2243      } else {
2244        this.mtime = readLong(in);
2245        this.atime = readLong(in);
2246      }
2247      this.permissionStatus = PermissionStatus.read(in);
2248      
2249      // read RPC ids if necessary
2250      readRpcIds(in, logVersion);
2251    }
2252
2253    @Override
2254    public String toString() {
2255      StringBuilder builder = new StringBuilder();
2256      builder.append("SymlinkOp [length=");
2257      builder.append(length);
2258      builder.append(", inodeId=");
2259      builder.append(inodeId);
2260      builder.append(", path=");
2261      builder.append(path);
2262      builder.append(", value=");
2263      builder.append(value);
2264      builder.append(", mtime=");
2265      builder.append(mtime);
2266      builder.append(", atime=");
2267      builder.append(atime);
2268      builder.append(", permissionStatus=");
2269      builder.append(permissionStatus);
2270      appendRpcIdsToString(builder, rpcClientId, rpcCallId);
2271      builder.append(", opCode=");
2272      builder.append(opCode);
2273      builder.append(", txid=");
2274      builder.append(txid);
2275      builder.append("]");
2276      return builder.toString();
2277    }
2278    
2279    @Override
2280    protected void toXml(ContentHandler contentHandler) throws SAXException {
2281      XMLUtils.addSaxString(contentHandler, "LENGTH",
2282          Integer.toString(length));
2283      XMLUtils.addSaxString(contentHandler, "INODEID",
2284          Long.toString(inodeId));
2285      XMLUtils.addSaxString(contentHandler, "PATH", path);
2286      XMLUtils.addSaxString(contentHandler, "VALUE", value);
2287      XMLUtils.addSaxString(contentHandler, "MTIME",
2288          Long.toString(mtime));
2289      XMLUtils.addSaxString(contentHandler, "ATIME",
2290          Long.toString(atime));
2291      FSEditLogOp.permissionStatusToXml(contentHandler, permissionStatus);
2292      appendRpcIdsToXml(contentHandler, rpcClientId, rpcCallId);
2293    }
2294
2295    @Override 
2296    void fromXml(Stanza st) throws InvalidXmlException {
2297      this.length = Integer.parseInt(st.getValue("LENGTH"));
2298      this.inodeId = Long.parseLong(st.getValue("INODEID"));
2299      this.path = st.getValue("PATH");
2300      this.value = st.getValue("VALUE");
2301      this.mtime = Long.parseLong(st.getValue("MTIME"));
2302      this.atime = Long.parseLong(st.getValue("ATIME"));
2303      this.permissionStatus = permissionStatusFromXml(st);
2304      
2305      readRpcIdsFromXml(st);
2306    }
2307  }
2308
2309  /** {@literal @AtMostOnce} for {@link ClientProtocol#rename2} */
2310  static class RenameOp extends FSEditLogOp {
2311    int length;
2312    String src;
2313    String dst;
2314    long timestamp;
2315    Rename[] options;
2316
2317    private RenameOp() {
2318      super(OP_RENAME);
2319    }
2320
2321    static RenameOp getInstance(OpInstanceCache cache) {
2322      return (RenameOp)cache.get(OP_RENAME);
2323    }
2324
2325    RenameOp setSource(String src) {
2326      this.src = src;
2327      return this;
2328    }
2329
2330    RenameOp setDestination(String dst) {
2331      this.dst = dst;
2332      return this;
2333    }
2334    
2335    RenameOp setTimestamp(long timestamp) {
2336      this.timestamp = timestamp;
2337      return this;
2338    }
2339    
2340    RenameOp setOptions(Rename[] options) {
2341      this.options = options;
2342      return this;
2343    }
2344
2345    @Override
2346    public 
2347    void writeFields(DataOutputStream out) throws IOException {
2348      FSImageSerialization.writeString(src, out);
2349      FSImageSerialization.writeString(dst, out);
2350      FSImageSerialization.writeLong(timestamp, out);
2351      toBytesWritable(options).write(out);
2352      writeRpcIds(rpcClientId, rpcCallId, out);
2353    }
2354
2355    @Override
2356    void readFields(DataInputStream in, int logVersion)
2357        throws IOException {
2358      if (!NameNodeLayoutVersion.supports(
2359          LayoutVersion.Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
2360        this.length = in.readInt();
2361        if (this.length != 3) {
2362          throw new IOException("Incorrect data format. " + "Rename operation.");
2363        }
2364      }
2365      this.src = FSImageSerialization.readString(in);
2366      this.dst = FSImageSerialization.readString(in);
2367
2368      if (NameNodeLayoutVersion.supports(
2369          LayoutVersion.Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
2370        this.timestamp = FSImageSerialization.readLong(in);
2371      } else {
2372        this.timestamp = readLong(in);
2373      }
2374      this.options = readRenameOptions(in);
2375      
2376      // read RPC ids if necessary
2377      readRpcIds(in, logVersion);
2378    }
2379
2380    private static Rename[] readRenameOptions(DataInputStream in) throws IOException {
2381      BytesWritable writable = new BytesWritable();
2382      writable.readFields(in);
2383
2384      byte[] bytes = writable.getBytes();
2385      Rename[] options = new Rename[bytes.length];
2386
2387      for (int i = 0; i < bytes.length; i++) {
2388        options[i] = Rename.valueOf(bytes[i]);
2389      }
2390      return options;
2391    }
2392
2393    static BytesWritable toBytesWritable(Rename... options) {
2394      byte[] bytes = new byte[options.length];
2395      for (int i = 0; i < options.length; i++) {
2396        bytes[i] = options[i].value();
2397      }
2398      return new BytesWritable(bytes);
2399    }
2400
2401    @Override
2402    public String toString() {
2403      StringBuilder builder = new StringBuilder();
2404      builder.append("RenameOp [length=");
2405      builder.append(length);
2406      builder.append(", src=");
2407      builder.append(src);
2408      builder.append(", dst=");
2409      builder.append(dst);
2410      builder.append(", timestamp=");
2411      builder.append(timestamp);
2412      builder.append(", options=");
2413      builder.append(Arrays.toString(options));
2414      appendRpcIdsToString(builder, rpcClientId, rpcCallId);
2415      builder.append(", opCode=");
2416      builder.append(opCode);
2417      builder.append(", txid=");
2418      builder.append(txid);
2419      builder.append("]");
2420      return builder.toString();
2421    }
2422    
2423    @Override
2424    protected void toXml(ContentHandler contentHandler) throws SAXException {
2425      XMLUtils.addSaxString(contentHandler, "LENGTH",
2426          Integer.toString(length));
2427      XMLUtils.addSaxString(contentHandler, "SRC", src);
2428      XMLUtils.addSaxString(contentHandler, "DST", dst);
2429      XMLUtils.addSaxString(contentHandler, "TIMESTAMP",
2430          Long.toString(timestamp));
2431      StringBuilder bld = new StringBuilder();
2432      String prefix = "";
2433      for (Rename r : options) {
2434        bld.append(prefix).append(r.toString());
2435        prefix = "|";
2436      }
2437      XMLUtils.addSaxString(contentHandler, "OPTIONS", bld.toString());
2438      appendRpcIdsToXml(contentHandler, rpcClientId, rpcCallId);
2439    }
2440    
2441    @Override void fromXml(Stanza st) throws InvalidXmlException {
2442      this.length = Integer.parseInt(st.getValue("LENGTH"));
2443      this.src = st.getValue("SRC");
2444      this.dst = st.getValue("DST");
2445      this.timestamp = Long.parseLong(st.getValue("TIMESTAMP"));
2446      String opts = st.getValue("OPTIONS");
2447      String o[] = opts.split("\\|");
2448      this.options = new Rename[o.length];
2449      for (int i = 0; i < o.length; i++) {
2450        if (o[i].equals(""))
2451          continue;
2452        try {
2453          this.options[i] = Rename.valueOf(o[i]);
2454        } finally {
2455          if (this.options[i] == null) {
2456            System.err.println("error parsing Rename value: \"" + o[i] + "\"");
2457          }
2458        }
2459      }
2460      readRpcIdsFromXml(st);
2461    }
2462  }
2463 
2464  /**
2465   * {@literal @Idempotent} for {@link ClientProtocol#recoverLease}. In the
2466   * meanwhile, startFile and appendFile both have their own corresponding
2467   * editlog op.
2468   */
2469  static class ReassignLeaseOp extends FSEditLogOp {
2470    String leaseHolder;
2471    String path;
2472    String newHolder;
2473
2474    private ReassignLeaseOp() {
2475      super(OP_REASSIGN_LEASE);
2476    }
2477
2478    static ReassignLeaseOp getInstance(OpInstanceCache cache) {
2479      return (ReassignLeaseOp)cache.get(OP_REASSIGN_LEASE);
2480    }
2481
2482    ReassignLeaseOp setLeaseHolder(String leaseHolder) {
2483      this.leaseHolder = leaseHolder;
2484      return this;
2485    }
2486
2487    ReassignLeaseOp setPath(String path) {
2488      this.path = path;
2489      return this;
2490    }
2491
2492    ReassignLeaseOp setNewHolder(String newHolder) {
2493      this.newHolder = newHolder;
2494      return this;
2495    }
2496
2497    @Override
2498    public 
2499    void writeFields(DataOutputStream out) throws IOException {
2500      FSImageSerialization.writeString(leaseHolder, out);
2501      FSImageSerialization.writeString(path, out);
2502      FSImageSerialization.writeString(newHolder, out);
2503    }
2504
2505    @Override
2506    void readFields(DataInputStream in, int logVersion)
2507        throws IOException {
2508      this.leaseHolder = FSImageSerialization.readString(in);
2509      this.path = FSImageSerialization.readString(in);
2510      this.newHolder = FSImageSerialization.readString(in);
2511    }
2512
2513    @Override
2514    public String toString() {
2515      StringBuilder builder = new StringBuilder();
2516      builder.append("ReassignLeaseOp [leaseHolder=");
2517      builder.append(leaseHolder);
2518      builder.append(", path=");
2519      builder.append(path);
2520      builder.append(", newHolder=");
2521      builder.append(newHolder);
2522      builder.append(", opCode=");
2523      builder.append(opCode);
2524      builder.append(", txid=");
2525      builder.append(txid);
2526      builder.append("]");
2527      return builder.toString();
2528    }
2529    
2530    @Override
2531    protected void toXml(ContentHandler contentHandler) throws SAXException {
2532      XMLUtils.addSaxString(contentHandler, "LEASEHOLDER", leaseHolder);
2533      XMLUtils.addSaxString(contentHandler, "PATH", path);
2534      XMLUtils.addSaxString(contentHandler, "NEWHOLDER", newHolder);
2535    }
2536    
2537    @Override void fromXml(Stanza st) throws InvalidXmlException {
2538      this.leaseHolder = st.getValue("LEASEHOLDER");
2539      this.path = st.getValue("PATH");
2540      this.newHolder = st.getValue("NEWHOLDER");
2541    }
2542  }
2543
2544  /** {@literal @Idempotent} for {@link ClientProtocol#getDelegationToken} */
2545  static class GetDelegationTokenOp extends FSEditLogOp {
2546    DelegationTokenIdentifier token;
2547    long expiryTime;
2548
2549    private GetDelegationTokenOp() {
2550      super(OP_GET_DELEGATION_TOKEN);
2551    }
2552
2553    static GetDelegationTokenOp getInstance(OpInstanceCache cache) {
2554      return (GetDelegationTokenOp)cache.get(OP_GET_DELEGATION_TOKEN);
2555    }
2556
2557    GetDelegationTokenOp setDelegationTokenIdentifier(
2558        DelegationTokenIdentifier token) {
2559      this.token = token;
2560      return this;
2561    }
2562
2563    GetDelegationTokenOp setExpiryTime(long expiryTime) {
2564      this.expiryTime = expiryTime;
2565      return this;
2566    }
2567
2568    @Override
2569    public 
2570    void writeFields(DataOutputStream out) throws IOException {
2571      token.write(out);
2572      FSImageSerialization.writeLong(expiryTime, out);
2573    }
2574
2575    @Override
2576    void readFields(DataInputStream in, int logVersion)
2577        throws IOException {
2578      this.token = new DelegationTokenIdentifier();
2579      this.token.readFields(in);
2580      if (NameNodeLayoutVersion.supports(
2581          LayoutVersion.Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
2582        this.expiryTime = FSImageSerialization.readLong(in);
2583      } else {
2584        this.expiryTime = readLong(in);
2585      }
2586    }
2587
2588    @Override
2589    public String toString() {
2590      StringBuilder builder = new StringBuilder();
2591      builder.append("GetDelegationTokenOp [token=");
2592      builder.append(token);
2593      builder.append(", expiryTime=");
2594      builder.append(expiryTime);
2595      builder.append(", opCode=");
2596      builder.append(opCode);
2597      builder.append(", txid=");
2598      builder.append(txid);
2599      builder.append("]");
2600      return builder.toString();
2601    }
2602    
2603    @Override
2604    protected void toXml(ContentHandler contentHandler) throws SAXException {
2605      FSEditLogOp.delegationTokenToXml(contentHandler, token);
2606      XMLUtils.addSaxString(contentHandler, "EXPIRY_TIME",
2607          Long.toString(expiryTime));
2608    }
2609    
2610    @Override void fromXml(Stanza st) throws InvalidXmlException {
2611      this.token = delegationTokenFromXml(st.getChildren(
2612          "DELEGATION_TOKEN_IDENTIFIER").get(0));
2613      this.expiryTime = Long.parseLong(st.getValue("EXPIRY_TIME"));
2614    }
2615  }
2616
2617  /** {@literal @Idempotent} for {@link ClientProtocol#renewDelegationToken} */
2618  static class RenewDelegationTokenOp extends FSEditLogOp {
2619    DelegationTokenIdentifier token;
2620    long expiryTime;
2621
2622    private RenewDelegationTokenOp() {
2623      super(OP_RENEW_DELEGATION_TOKEN);
2624    }
2625
2626    static RenewDelegationTokenOp getInstance(OpInstanceCache cache) {
2627      return (RenewDelegationTokenOp)cache.get(OP_RENEW_DELEGATION_TOKEN);
2628    }
2629
2630    RenewDelegationTokenOp setDelegationTokenIdentifier(
2631        DelegationTokenIdentifier token) {
2632      this.token = token;
2633      return this;
2634    }
2635
2636    RenewDelegationTokenOp setExpiryTime(long expiryTime) {
2637      this.expiryTime = expiryTime;
2638      return this;
2639    }
2640
2641    @Override
2642    public 
2643    void writeFields(DataOutputStream out) throws IOException {
2644      token.write(out);
2645      FSImageSerialization.writeLong(expiryTime, out);
2646    }
2647
2648    @Override
2649    void readFields(DataInputStream in, int logVersion)
2650        throws IOException {
2651      this.token = new DelegationTokenIdentifier();
2652      this.token.readFields(in);
2653      if (NameNodeLayoutVersion.supports(
2654          LayoutVersion.Feature.EDITLOG_OP_OPTIMIZATION, logVersion)) {
2655        this.expiryTime = FSImageSerialization.readLong(in);
2656      } else {
2657        this.expiryTime = readLong(in);
2658      }
2659    }
2660
2661    @Override
2662    public String toString() {
2663      StringBuilder builder = new StringBuilder();
2664      builder.append("RenewDelegationTokenOp [token=");
2665      builder.append(token);
2666      builder.append(", expiryTime=");
2667      builder.append(expiryTime);
2668      builder.append(", opCode=");
2669      builder.append(opCode);
2670      builder.append(", txid=");
2671      builder.append(txid);
2672      builder.append("]");
2673      return builder.toString();
2674    }
2675    
2676    @Override
2677    protected void toXml(ContentHandler contentHandler) throws SAXException {
2678      FSEditLogOp.delegationTokenToXml(contentHandler, token);
2679      XMLUtils.addSaxString(contentHandler, "EXPIRY_TIME",
2680          Long.toString(expiryTime));
2681    }
2682    
2683    @Override void fromXml(Stanza st) throws InvalidXmlException {
2684      this.token = delegationTokenFromXml(st.getChildren(
2685          "DELEGATION_TOKEN_IDENTIFIER").get(0));
2686      this.expiryTime = Long.parseLong(st.getValue("EXPIRY_TIME"));
2687    }
2688  }
2689
2690  /** {@literal @Idempotent} for {@link ClientProtocol#cancelDelegationToken} */
2691  static class CancelDelegationTokenOp extends FSEditLogOp {
2692    DelegationTokenIdentifier token;
2693
2694    private CancelDelegationTokenOp() {
2695      super(OP_CANCEL_DELEGATION_TOKEN);
2696    }
2697
2698    static CancelDelegationTokenOp getInstance(OpInstanceCache cache) {
2699      return (CancelDelegationTokenOp)cache.get(OP_CANCEL_DELEGATION_TOKEN);
2700    }
2701
2702    CancelDelegationTokenOp setDelegationTokenIdentifier(
2703        DelegationTokenIdentifier token) {
2704      this.token = token;
2705      return this;
2706    }
2707
2708    @Override
2709    public 
2710    void writeFields(DataOutputStream out) throws IOException {
2711      token.write(out);
2712    }
2713
2714    @Override
2715    void readFields(DataInputStream in, int logVersion)
2716        throws IOException {
2717      this.token = new DelegationTokenIdentifier();
2718      this.token.readFields(in);
2719    }
2720
2721    @Override
2722    public String toString() {
2723      StringBuilder builder = new StringBuilder();
2724      builder.append("CancelDelegationTokenOp [token=");
2725      builder.append(token);
2726      builder.append(", opCode=");
2727      builder.append(opCode);
2728      builder.append(", txid=");
2729      builder.append(txid);
2730      builder.append("]");
2731      return builder.toString();
2732    }
2733    
2734    @Override
2735    protected void toXml(ContentHandler contentHandler) throws SAXException {
2736      FSEditLogOp.delegationTokenToXml(contentHandler, token);
2737    }
2738    
2739    @Override void fromXml(Stanza st) throws InvalidXmlException {
2740      this.token = delegationTokenFromXml(st.getChildren(
2741          "DELEGATION_TOKEN_IDENTIFIER").get(0));
2742    }
2743  }
2744
2745  static class UpdateMasterKeyOp extends FSEditLogOp {
2746    DelegationKey key;
2747
2748    private UpdateMasterKeyOp() {
2749      super(OP_UPDATE_MASTER_KEY);
2750    }
2751
2752    static UpdateMasterKeyOp getInstance(OpInstanceCache cache) {
2753      return (UpdateMasterKeyOp)cache.get(OP_UPDATE_MASTER_KEY);
2754    }
2755
2756    UpdateMasterKeyOp setDelegationKey(DelegationKey key) {
2757      this.key = key;
2758      return this;
2759    }
2760    
2761    @Override
2762    public 
2763    void writeFields(DataOutputStream out) throws IOException {
2764      key.write(out);
2765    }
2766
2767    @Override
2768    void readFields(DataInputStream in, int logVersion)
2769        throws IOException {
2770      this.key = new DelegationKey();
2771      this.key.readFields(in);
2772    }
2773
2774    @Override
2775    public String toString() {
2776      StringBuilder builder = new StringBuilder();
2777      builder.append("UpdateMasterKeyOp [key=");
2778      builder.append(key);
2779      builder.append(", opCode=");
2780      builder.append(opCode);
2781      builder.append(", txid=");
2782      builder.append(txid);
2783      builder.append("]");
2784      return builder.toString();
2785    }
2786    
2787    @Override
2788    protected void toXml(ContentHandler contentHandler) throws SAXException {
2789      FSEditLogOp.delegationKeyToXml(contentHandler, key);
2790    }
2791    
2792    @Override void fromXml(Stanza st) throws InvalidXmlException {
2793      this.key = delegationKeyFromXml(st.getChildren(
2794          "DELEGATION_KEY").get(0));
2795    }
2796  }
2797  
2798  static class LogSegmentOp extends FSEditLogOp {
2799    private LogSegmentOp(FSEditLogOpCodes code) {
2800      super(code);
2801      assert code == OP_START_LOG_SEGMENT ||
2802             code == OP_END_LOG_SEGMENT : "Bad op: " + code;
2803    }
2804
2805    static LogSegmentOp getInstance(OpInstanceCache cache,
2806        FSEditLogOpCodes code) {
2807      return (LogSegmentOp)cache.get(code);
2808    }
2809
2810    @Override
2811    public void readFields(DataInputStream in, int logVersion)
2812        throws IOException {
2813      // no data stored in these ops yet
2814    }
2815
2816    @Override
2817    public
2818    void writeFields(DataOutputStream out) throws IOException {
2819      // no data stored
2820    }
2821
2822    @Override
2823    public String toString() {
2824      StringBuilder builder = new StringBuilder();
2825      builder.append("LogSegmentOp [opCode=");
2826      builder.append(opCode);
2827      builder.append(", txid=");
2828      builder.append(txid);
2829      builder.append("]");
2830      return builder.toString();
2831    }
2832
2833    @Override
2834    protected void toXml(ContentHandler contentHandler) throws SAXException {
2835      // no data stored
2836    }
2837    
2838    @Override void fromXml(Stanza st) throws InvalidXmlException {
2839      // do nothing
2840    }
2841  }
2842
2843  static class InvalidOp extends FSEditLogOp {
2844    private InvalidOp() {
2845      super(OP_INVALID);
2846    }
2847
2848    static InvalidOp getInstance(OpInstanceCache cache) {
2849      return (InvalidOp)cache.get(OP_INVALID);
2850    }
2851
2852    @Override
2853    public 
2854    void writeFields(DataOutputStream out) throws IOException {
2855    }
2856    
2857    @Override
2858    void readFields(DataInputStream in, int logVersion)
2859        throws IOException {
2860      // nothing to read
2861    }
2862
2863    @Override
2864    public String toString() {
2865      StringBuilder builder = new StringBuilder();
2866      builder.append("InvalidOp [opCode=");
2867      builder.append(opCode);
2868      builder.append(", txid=");
2869      builder.append(txid);
2870      builder.append("]");
2871      return builder.toString();
2872    }
2873    @Override
2874    protected void toXml(ContentHandler contentHandler) throws SAXException {
2875      // no data stored
2876    }
2877    
2878    @Override void fromXml(Stanza st) throws InvalidXmlException {
2879      // do nothing
2880    }
2881  }
2882
2883  /**
2884   * Operation corresponding to creating a snapshot.
2885   * {@literal @AtMostOnce} for {@link ClientProtocol#createSnapshot}.
2886   */
2887  static class CreateSnapshotOp extends FSEditLogOp {
2888    String snapshotRoot;
2889    String snapshotName;
2890    
2891    public CreateSnapshotOp() {
2892      super(OP_CREATE_SNAPSHOT);
2893    }
2894    
2895    static CreateSnapshotOp getInstance(OpInstanceCache cache) {
2896      return (CreateSnapshotOp)cache.get(OP_CREATE_SNAPSHOT);
2897    }
2898    
2899    CreateSnapshotOp setSnapshotName(String snapName) {
2900      this.snapshotName = snapName;
2901      return this;
2902    }
2903
2904    public CreateSnapshotOp setSnapshotRoot(String snapRoot) {
2905      snapshotRoot = snapRoot;
2906      return this;
2907    }
2908    
2909    @Override
2910    void readFields(DataInputStream in, int logVersion) throws IOException {
2911      snapshotRoot = FSImageSerialization.readString(in);
2912      snapshotName = FSImageSerialization.readString(in);
2913      
2914      // read RPC ids if necessary
2915      readRpcIds(in, logVersion);
2916    }
2917
2918    @Override
2919    public void writeFields(DataOutputStream out) throws IOException {
2920      FSImageSerialization.writeString(snapshotRoot, out);
2921      FSImageSerialization.writeString(snapshotName, out);
2922      writeRpcIds(rpcClientId, rpcCallId, out);
2923    }
2924
2925    @Override
2926    protected void toXml(ContentHandler contentHandler) throws SAXException {
2927      XMLUtils.addSaxString(contentHandler, "SNAPSHOTROOT", snapshotRoot);
2928      XMLUtils.addSaxString(contentHandler, "SNAPSHOTNAME", snapshotName);
2929      appendRpcIdsToXml(contentHandler, rpcClientId, rpcCallId);
2930    }
2931
2932    @Override
2933    void fromXml(Stanza st) throws InvalidXmlException {
2934      snapshotRoot = st.getValue("SNAPSHOTROOT");
2935      snapshotName = st.getValue("SNAPSHOTNAME");
2936      
2937      readRpcIdsFromXml(st);
2938    }
2939    
2940    @Override
2941    public String toString() {
2942      StringBuilder builder = new StringBuilder();
2943      builder.append("CreateSnapshotOp [snapshotRoot=");
2944      builder.append(snapshotRoot);
2945      builder.append(", snapshotName=");
2946      builder.append(snapshotName);
2947      appendRpcIdsToString(builder, rpcClientId, rpcCallId);
2948      builder.append("]");
2949      return builder.toString();
2950    }
2951  }
2952  
2953  /**
2954   * Operation corresponding to delete a snapshot.
2955   * {@literal @AtMostOnce} for {@link ClientProtocol#deleteSnapshot}.
2956   */
2957  static class DeleteSnapshotOp extends FSEditLogOp {
2958    String snapshotRoot;
2959    String snapshotName;
2960    
2961    DeleteSnapshotOp() {
2962      super(OP_DELETE_SNAPSHOT);
2963    }
2964    
2965    static DeleteSnapshotOp getInstance(OpInstanceCache cache) {
2966      return (DeleteSnapshotOp)cache.get(OP_DELETE_SNAPSHOT);
2967    }
2968    
2969    DeleteSnapshotOp setSnapshotName(String snapName) {
2970      this.snapshotName = snapName;
2971      return this;
2972    }
2973
2974    DeleteSnapshotOp setSnapshotRoot(String snapRoot) {
2975      snapshotRoot = snapRoot;
2976      return this;
2977    }
2978    
2979    @Override
2980    void readFields(DataInputStream in, int logVersion) throws IOException {
2981      snapshotRoot = FSImageSerialization.readString(in);
2982      snapshotName = FSImageSerialization.readString(in);
2983      
2984      // read RPC ids if necessary
2985      readRpcIds(in, logVersion);
2986    }
2987
2988    @Override
2989    public void writeFields(DataOutputStream out) throws IOException {
2990      FSImageSerialization.writeString(snapshotRoot, out);
2991      FSImageSerialization.writeString(snapshotName, out);
2992      writeRpcIds(rpcClientId, rpcCallId, out);
2993    }
2994
2995    @Override
2996    protected void toXml(ContentHandler contentHandler) throws SAXException {
2997      XMLUtils.addSaxString(contentHandler, "SNAPSHOTROOT", snapshotRoot);
2998      XMLUtils.addSaxString(contentHandler, "SNAPSHOTNAME", snapshotName);
2999      appendRpcIdsToXml(contentHandler, rpcClientId, rpcCallId);
3000    }
3001
3002    @Override
3003    void fromXml(Stanza st) throws InvalidXmlException {
3004      snapshotRoot = st.getValue("SNAPSHOTROOT");
3005      snapshotName = st.getValue("SNAPSHOTNAME");
3006      
3007      readRpcIdsFromXml(st);
3008    }
3009    
3010    @Override
3011    public String toString() {
3012      StringBuilder builder = new StringBuilder();
3013      builder.append("DeleteSnapshotOp [snapshotRoot=");
3014      builder.append(snapshotRoot);
3015      builder.append(", snapshotName=");
3016      builder.append(snapshotName);
3017      appendRpcIdsToString(builder, rpcClientId, rpcCallId);
3018      builder.append("]");
3019      return builder.toString();
3020    }
3021  }
3022  
3023  /**
3024   * Operation corresponding to rename a snapshot.
3025   * {@literal @AtMostOnce} for {@link ClientProtocol#renameSnapshot}.
3026   */
3027  static class RenameSnapshotOp extends FSEditLogOp {
3028    String snapshotRoot;
3029    String snapshotOldName;
3030    String snapshotNewName;
3031    
3032    RenameSnapshotOp() {
3033      super(OP_RENAME_SNAPSHOT);
3034    }
3035    
3036    static RenameSnapshotOp getInstance(OpInstanceCache cache) {
3037      return (RenameSnapshotOp) cache.get(OP_RENAME_SNAPSHOT);
3038    }
3039    
3040    RenameSnapshotOp setSnapshotOldName(String snapshotOldName) {
3041      this.snapshotOldName = snapshotOldName;
3042      return this;
3043    }
3044
3045    RenameSnapshotOp setSnapshotNewName(String snapshotNewName) {
3046      this.snapshotNewName = snapshotNewName;
3047      return this;
3048    }
3049    
3050    RenameSnapshotOp setSnapshotRoot(String snapshotRoot) {
3051      this.snapshotRoot = snapshotRoot;
3052      return this;
3053    }
3054    
3055    @Override
3056    void readFields(DataInputStream in, int logVersion) throws IOException {
3057      snapshotRoot = FSImageSerialization.readString(in);
3058      snapshotOldName = FSImageSerialization.readString(in);
3059      snapshotNewName = FSImageSerialization.readString(in);
3060      
3061      // read RPC ids if necessary
3062      readRpcIds(in, logVersion);
3063    }
3064
3065    @Override
3066    public void writeFields(DataOutputStream out) throws IOException {
3067      FSImageSerialization.writeString(snapshotRoot, out);
3068      FSImageSerialization.writeString(snapshotOldName, out);
3069      FSImageSerialization.writeString(snapshotNewName, out);
3070      
3071      writeRpcIds(rpcClientId, rpcCallId, out);
3072    }
3073
3074    @Override
3075    protected void toXml(ContentHandler contentHandler) throws SAXException {
3076      XMLUtils.addSaxString(contentHandler, "SNAPSHOTROOT", snapshotRoot);
3077      XMLUtils.addSaxString(contentHandler, "SNAPSHOTOLDNAME", snapshotOldName);
3078      XMLUtils.addSaxString(contentHandler, "SNAPSHOTNEWNAME", snapshotNewName);
3079      appendRpcIdsToXml(contentHandler, rpcClientId, rpcCallId);
3080    }
3081
3082    @Override
3083    void fromXml(Stanza st) throws InvalidXmlException {
3084      snapshotRoot = st.getValue("SNAPSHOTROOT");
3085      snapshotOldName = st.getValue("SNAPSHOTOLDNAME");
3086      snapshotNewName = st.getValue("SNAPSHOTNEWNAME");
3087      
3088      readRpcIdsFromXml(st);
3089    }
3090    
3091    @Override
3092    public String toString() {
3093      StringBuilder builder = new StringBuilder();
3094      builder.append("RenameSnapshotOp [snapshotRoot=");
3095      builder.append(snapshotRoot);
3096      builder.append(", snapshotOldName=");
3097      builder.append(snapshotOldName);
3098      builder.append(", snapshotNewName=");
3099      builder.append(snapshotNewName);
3100      appendRpcIdsToString(builder, rpcClientId, rpcCallId);
3101      builder.append("]");
3102      return builder.toString();
3103    }
3104  }
3105
3106  /**
3107   * Operation corresponding to allow creating snapshot on a directory
3108   */
3109  static class AllowSnapshotOp extends FSEditLogOp { // @Idempotent
3110    String snapshotRoot;
3111
3112    public AllowSnapshotOp() {
3113      super(OP_ALLOW_SNAPSHOT);
3114    }
3115
3116    public AllowSnapshotOp(String snapRoot) {
3117      super(OP_ALLOW_SNAPSHOT);
3118      snapshotRoot = snapRoot;
3119    }
3120
3121    static AllowSnapshotOp getInstance(OpInstanceCache cache) {
3122      return (AllowSnapshotOp) cache.get(OP_ALLOW_SNAPSHOT);
3123    }
3124
3125    public AllowSnapshotOp setSnapshotRoot(String snapRoot) {
3126      snapshotRoot = snapRoot;
3127      return this;
3128    }
3129
3130    @Override
3131    void readFields(DataInputStream in, int logVersion) throws IOException {
3132      snapshotRoot = FSImageSerialization.readString(in);
3133    }
3134
3135    @Override
3136    public void writeFields(DataOutputStream out) throws IOException {
3137      FSImageSerialization.writeString(snapshotRoot, out);
3138    }
3139
3140    @Override
3141    protected void toXml(ContentHandler contentHandler) throws SAXException {
3142      XMLUtils.addSaxString(contentHandler, "SNAPSHOTROOT", snapshotRoot);
3143    }
3144
3145    @Override
3146    void fromXml(Stanza st) throws InvalidXmlException {
3147      snapshotRoot = st.getValue("SNAPSHOTROOT");
3148    }
3149
3150    @Override
3151    public String toString() {
3152      StringBuilder builder = new StringBuilder();
3153      builder.append("AllowSnapshotOp [snapshotRoot=");
3154      builder.append(snapshotRoot);
3155      builder.append("]");
3156      return builder.toString();
3157    }
3158  }
3159
3160  /**
3161   * Operation corresponding to disallow creating snapshot on a directory
3162   */
3163  static class DisallowSnapshotOp extends FSEditLogOp { // @Idempotent
3164    String snapshotRoot;
3165
3166    public DisallowSnapshotOp() {
3167      super(OP_DISALLOW_SNAPSHOT);
3168    }
3169
3170    public DisallowSnapshotOp(String snapRoot) {
3171      super(OP_DISALLOW_SNAPSHOT);
3172      snapshotRoot = snapRoot;
3173    }
3174
3175    static DisallowSnapshotOp getInstance(OpInstanceCache cache) {
3176      return (DisallowSnapshotOp) cache.get(OP_DISALLOW_SNAPSHOT);
3177    }
3178
3179    public DisallowSnapshotOp setSnapshotRoot(String snapRoot) {
3180      snapshotRoot = snapRoot;
3181      return this;
3182    }
3183
3184    @Override
3185    void readFields(DataInputStream in, int logVersion) throws IOException {
3186      snapshotRoot = FSImageSerialization.readString(in);
3187    }
3188
3189    @Override
3190    public void writeFields(DataOutputStream out) throws IOException {
3191      FSImageSerialization.writeString(snapshotRoot, out);
3192    }
3193
3194    @Override
3195    protected void toXml(ContentHandler contentHandler) throws SAXException {
3196      XMLUtils.addSaxString(contentHandler, "SNAPSHOTROOT", snapshotRoot);
3197    }
3198
3199    @Override
3200    void fromXml(Stanza st) throws InvalidXmlException {
3201      snapshotRoot = st.getValue("SNAPSHOTROOT");
3202    }
3203
3204    @Override
3205    public String toString() {
3206      StringBuilder builder = new StringBuilder();
3207      builder.append("DisallowSnapshotOp [snapshotRoot=");
3208      builder.append(snapshotRoot);
3209      builder.append("]");
3210      return builder.toString();
3211    }
3212  }
3213
3214  /**
3215   * {@literal @AtMostOnce} for
3216   * {@link ClientProtocol#addCacheDirective}
3217   */
3218  static class AddCacheDirectiveInfoOp extends FSEditLogOp {
3219    CacheDirectiveInfo directive;
3220
3221    public AddCacheDirectiveInfoOp() {
3222      super(OP_ADD_CACHE_DIRECTIVE);
3223    }
3224
3225    static AddCacheDirectiveInfoOp getInstance(OpInstanceCache cache) {
3226      return (AddCacheDirectiveInfoOp) cache
3227          .get(OP_ADD_CACHE_DIRECTIVE);
3228    }
3229
3230    public AddCacheDirectiveInfoOp setDirective(
3231        CacheDirectiveInfo directive) {
3232      this.directive = directive;
3233      assert(directive.getId() != null);
3234      assert(directive.getPath() != null);
3235      assert(directive.getReplication() != null);
3236      assert(directive.getPool() != null);
3237      assert(directive.getExpiration() != null);
3238      return this;
3239    }
3240
3241    @Override
3242    void readFields(DataInputStream in, int logVersion) throws IOException {
3243      directive = FSImageSerialization.readCacheDirectiveInfo(in);
3244      readRpcIds(in, logVersion);
3245    }
3246
3247    @Override
3248    public void writeFields(DataOutputStream out) throws IOException {
3249      FSImageSerialization.writeCacheDirectiveInfo(out, directive);
3250      writeRpcIds(rpcClientId, rpcCallId, out);
3251    }
3252
3253    @Override
3254    protected void toXml(ContentHandler contentHandler) throws SAXException {
3255      FSImageSerialization.writeCacheDirectiveInfo(contentHandler, directive);
3256      appendRpcIdsToXml(contentHandler, rpcClientId, rpcCallId);
3257    }
3258
3259    @Override
3260    void fromXml(Stanza st) throws InvalidXmlException {
3261      directive = FSImageSerialization.readCacheDirectiveInfo(st);
3262      readRpcIdsFromXml(st);
3263    }
3264
3265    @Override
3266    public String toString() {
3267      StringBuilder builder = new StringBuilder();
3268      builder.append("AddCacheDirectiveInfo [");
3269      builder.append("id=" + directive.getId() + ",");
3270      builder.append("path=" + directive.getPath().toUri().getPath() + ",");
3271      builder.append("replication=" + directive.getReplication() + ",");
3272      builder.append("pool=" + directive.getPool() + ",");
3273      builder.append("expiration=" + directive.getExpiration().getMillis());
3274      appendRpcIdsToString(builder, rpcClientId, rpcCallId);
3275      builder.append("]");
3276      return builder.toString();
3277    }
3278  }
3279
3280  /**
3281   * {@literal @AtMostOnce} for
3282   * {@link ClientProtocol#modifyCacheDirective}
3283   */
3284  static class ModifyCacheDirectiveInfoOp extends FSEditLogOp {
3285    CacheDirectiveInfo directive;
3286
3287    public ModifyCacheDirectiveInfoOp() {
3288      super(OP_MODIFY_CACHE_DIRECTIVE);
3289    }
3290
3291    static ModifyCacheDirectiveInfoOp getInstance(OpInstanceCache cache) {
3292      return (ModifyCacheDirectiveInfoOp) cache
3293          .get(OP_MODIFY_CACHE_DIRECTIVE);
3294    }
3295
3296    public ModifyCacheDirectiveInfoOp setDirective(
3297        CacheDirectiveInfo directive) {
3298      this.directive = directive;
3299      assert(directive.getId() != null);
3300      return this;
3301    }
3302
3303    @Override
3304    void readFields(DataInputStream in, int logVersion) throws IOException {
3305      this.directive = FSImageSerialization.readCacheDirectiveInfo(in);
3306      readRpcIds(in, logVersion);
3307    }
3308
3309    @Override
3310    public void writeFields(DataOutputStream out) throws IOException {
3311      FSImageSerialization.writeCacheDirectiveInfo(out, directive);
3312      writeRpcIds(rpcClientId, rpcCallId, out);
3313    }
3314
3315    @Override
3316    protected void toXml(ContentHandler contentHandler) throws SAXException {
3317      FSImageSerialization.writeCacheDirectiveInfo(contentHandler, directive);
3318      appendRpcIdsToXml(contentHandler, rpcClientId, rpcCallId);
3319    }
3320
3321    @Override
3322    void fromXml(Stanza st) throws InvalidXmlException {
3323      this.directive = FSImageSerialization.readCacheDirectiveInfo(st);
3324      readRpcIdsFromXml(st);
3325    }
3326
3327    @Override
3328    public String toString() {
3329      StringBuilder builder = new StringBuilder();
3330      builder.append("ModifyCacheDirectiveInfoOp[");
3331      builder.append("id=").append(directive.getId());
3332      if (directive.getPath() != null) {
3333        builder.append(",").append("path=").append(directive.getPath());
3334      }
3335      if (directive.getReplication() != null) {
3336        builder.append(",").append("replication=").
3337            append(directive.getReplication());
3338      }
3339      if (directive.getPool() != null) {
3340        builder.append(",").append("pool=").append(directive.getPool());
3341      }
3342      if (directive.getExpiration() != null) {
3343        builder.append(",").append("expiration=").
3344            append(directive.getExpiration().getMillis());
3345      }
3346      appendRpcIdsToString(builder, rpcClientId, rpcCallId);
3347      builder.append("]");
3348      return builder.toString();
3349    }
3350  }
3351
3352  /**
3353   * {@literal @AtMostOnce} for
3354   * {@link ClientProtocol#removeCacheDirective}
3355   */
3356  static class RemoveCacheDirectiveInfoOp extends FSEditLogOp {
3357    long id;
3358
3359    public RemoveCacheDirectiveInfoOp() {
3360      super(OP_REMOVE_CACHE_DIRECTIVE);
3361    }
3362
3363    static RemoveCacheDirectiveInfoOp getInstance(OpInstanceCache cache) {
3364      return (RemoveCacheDirectiveInfoOp) cache
3365          .get(OP_REMOVE_CACHE_DIRECTIVE);
3366    }
3367
3368    public RemoveCacheDirectiveInfoOp setId(long id) {
3369      this.id = id;
3370      return this;
3371    }
3372
3373    @Override
3374    void readFields(DataInputStream in, int logVersion) throws IOException {
3375      this.id = FSImageSerialization.readLong(in);
3376      readRpcIds(in, logVersion);
3377    }
3378
3379    @Override
3380    public void writeFields(DataOutputStream out) throws IOException {
3381      FSImageSerialization.writeLong(id, out);
3382      writeRpcIds(rpcClientId, rpcCallId, out);
3383    }
3384
3385    @Override
3386    protected void toXml(ContentHandler contentHandler) throws SAXException {
3387      XMLUtils.addSaxString(contentHandler, "ID", Long.toString(id));
3388      appendRpcIdsToXml(contentHandler, rpcClientId, rpcCallId);
3389    }
3390
3391    @Override
3392    void fromXml(Stanza st) throws InvalidXmlException {
3393      this.id = Long.parseLong(st.getValue("ID"));
3394      readRpcIdsFromXml(st);
3395    }
3396
3397    @Override
3398    public String toString() {
3399      StringBuilder builder = new StringBuilder();
3400      builder.append("RemoveCacheDirectiveInfo [");
3401      builder.append("id=" + Long.toString(id));
3402      appendRpcIdsToString(builder, rpcClientId, rpcCallId);
3403      builder.append("]");
3404      return builder.toString();
3405    }
3406  }
3407
3408  /** {@literal @AtMostOnce} for {@link ClientProtocol#addCachePool} */
3409  static class AddCachePoolOp extends FSEditLogOp {
3410    CachePoolInfo info;
3411
3412    public AddCachePoolOp() {
3413      super(OP_ADD_CACHE_POOL);
3414    }
3415
3416    static AddCachePoolOp getInstance(OpInstanceCache cache) {
3417      return (AddCachePoolOp) cache.get(OP_ADD_CACHE_POOL);
3418    }
3419
3420    public AddCachePoolOp setPool(CachePoolInfo info) {
3421      this.info = info;
3422      assert(info.getPoolName() != null);
3423      assert(info.getOwnerName() != null);
3424      assert(info.getGroupName() != null);
3425      assert(info.getMode() != null);
3426      assert(info.getLimit() != null);
3427      return this;
3428    }
3429
3430    @Override
3431    void readFields(DataInputStream in, int logVersion) throws IOException {
3432      info = FSImageSerialization.readCachePoolInfo(in);
3433      readRpcIds(in, logVersion);
3434    }
3435
3436    @Override
3437    public void writeFields(DataOutputStream out) throws IOException {
3438      FSImageSerialization.writeCachePoolInfo(out, info);
3439      writeRpcIds(rpcClientId, rpcCallId, out);
3440    }
3441
3442    @Override
3443    protected void toXml(ContentHandler contentHandler) throws SAXException {
3444      FSImageSerialization.writeCachePoolInfo(contentHandler, info);
3445      appendRpcIdsToXml(contentHandler, rpcClientId, rpcCallId);
3446    }
3447
3448    @Override
3449    void fromXml(Stanza st) throws InvalidXmlException {
3450      this.info = FSImageSerialization.readCachePoolInfo(st);
3451      readRpcIdsFromXml(st);
3452    }
3453
3454    @Override
3455    public String toString() {
3456      StringBuilder builder = new StringBuilder();
3457      builder.append("AddCachePoolOp [");
3458      builder.append("poolName=" + info.getPoolName() + ",");
3459      builder.append("ownerName=" + info.getOwnerName() + ",");
3460      builder.append("groupName=" + info.getGroupName() + ",");
3461      builder.append("mode=" + Short.toString(info.getMode().toShort()) + ",");
3462      builder.append("limit=" + Long.toString(info.getLimit()));
3463      appendRpcIdsToString(builder, rpcClientId, rpcCallId);
3464      builder.append("]");
3465      return builder.toString();
3466    }
3467  }
3468
3469  /** {@literal @AtMostOnce} for {@link ClientProtocol#modifyCachePool} */
3470  static class ModifyCachePoolOp extends FSEditLogOp {
3471    CachePoolInfo info;
3472
3473    public ModifyCachePoolOp() {
3474      super(OP_MODIFY_CACHE_POOL);
3475    }
3476
3477    static ModifyCachePoolOp getInstance(OpInstanceCache cache) {
3478      return (ModifyCachePoolOp) cache.get(OP_MODIFY_CACHE_POOL);
3479    }
3480
3481    public ModifyCachePoolOp setInfo(CachePoolInfo info) {
3482      this.info = info;
3483      return this;
3484    }
3485
3486    @Override
3487    void readFields(DataInputStream in, int logVersion) throws IOException {
3488      info = FSImageSerialization.readCachePoolInfo(in);
3489      readRpcIds(in, logVersion);
3490    }
3491
3492    @Override
3493    public void writeFields(DataOutputStream out) throws IOException {
3494      FSImageSerialization.writeCachePoolInfo(out, info);
3495      writeRpcIds(rpcClientId, rpcCallId, out);
3496    }
3497
3498    @Override
3499    protected void toXml(ContentHandler contentHandler) throws SAXException {
3500      FSImageSerialization.writeCachePoolInfo(contentHandler, info);
3501      appendRpcIdsToXml(contentHandler, rpcClientId, rpcCallId);
3502    }
3503
3504    @Override
3505    void fromXml(Stanza st) throws InvalidXmlException {
3506      this.info = FSImageSerialization.readCachePoolInfo(st);
3507      readRpcIdsFromXml(st);
3508    }
3509
3510    @Override
3511    public String toString() {
3512      StringBuilder builder = new StringBuilder();
3513      builder.append("ModifyCachePoolOp [");
3514      ArrayList<String> fields = new ArrayList<String>(5);
3515      if (info.getPoolName() != null) {
3516        fields.add("poolName=" + info.getPoolName());
3517      }
3518      if (info.getOwnerName() != null) {
3519        fields.add("ownerName=" + info.getOwnerName());
3520      }
3521      if (info.getGroupName() != null) {
3522        fields.add("groupName=" + info.getGroupName());
3523      }
3524      if (info.getMode() != null) {
3525        fields.add("mode=" + info.getMode().toString());
3526      }
3527      if (info.getLimit() != null) {
3528        fields.add("limit=" + info.getLimit());
3529      }
3530      builder.append(Joiner.on(",").join(fields));
3531      appendRpcIdsToString(builder, rpcClientId, rpcCallId);
3532      builder.append("]");
3533      return builder.toString();
3534    }
3535  }
3536
3537  /** {@literal @AtMostOnce} for {@link ClientProtocol#removeCachePool} */
3538  static class RemoveCachePoolOp extends FSEditLogOp {
3539    String poolName;
3540
3541    public RemoveCachePoolOp() {
3542      super(OP_REMOVE_CACHE_POOL);
3543    }
3544
3545    static RemoveCachePoolOp getInstance(OpInstanceCache cache) {
3546      return (RemoveCachePoolOp) cache.get(OP_REMOVE_CACHE_POOL);
3547    }
3548
3549    public RemoveCachePoolOp setPoolName(String poolName) {
3550      this.poolName = poolName;
3551      return this;
3552    }
3553
3554    @Override
3555    void readFields(DataInputStream in, int logVersion) throws IOException {
3556      poolName = FSImageSerialization.readString(in);
3557      readRpcIds(in, logVersion);
3558    }
3559
3560    @Override
3561    public void writeFields(DataOutputStream out) throws IOException {
3562      FSImageSerialization.writeString(poolName, out);
3563      writeRpcIds(rpcClientId, rpcCallId, out);
3564    }
3565
3566    @Override
3567    protected void toXml(ContentHandler contentHandler) throws SAXException {
3568      XMLUtils.addSaxString(contentHandler, "POOLNAME", poolName);
3569      appendRpcIdsToXml(contentHandler, rpcClientId, rpcCallId);
3570    }
3571
3572    @Override
3573    void fromXml(Stanza st) throws InvalidXmlException {
3574      this.poolName = st.getValue("POOLNAME");
3575      readRpcIdsFromXml(st);
3576    }
3577
3578    @Override
3579    public String toString() {
3580      StringBuilder builder = new StringBuilder();
3581      builder.append("RemoveCachePoolOp [");
3582      builder.append("poolName=" + poolName);
3583      appendRpcIdsToString(builder, rpcClientId, rpcCallId);
3584      builder.append("]");
3585      return builder.toString();
3586    }
3587  }
3588  
3589  static class RemoveXAttrOp extends FSEditLogOp {
3590    List<XAttr> xAttrs;
3591    String src;
3592    
3593    private RemoveXAttrOp() {
3594      super(OP_REMOVE_XATTR);
3595    }
3596    
3597    static RemoveXAttrOp getInstance() {
3598      return new RemoveXAttrOp();
3599    }
3600
3601    @Override
3602    void readFields(DataInputStream in, int logVersion) throws IOException {
3603      XAttrEditLogProto p = XAttrEditLogProto.parseDelimitedFrom(in);
3604      src = p.getSrc();
3605      xAttrs = PBHelper.convertXAttrs(p.getXAttrsList());
3606      readRpcIds(in, logVersion);
3607    }
3608
3609    @Override
3610    public void writeFields(DataOutputStream out) throws IOException {
3611      XAttrEditLogProto.Builder b = XAttrEditLogProto.newBuilder();
3612      if (src != null) {
3613        b.setSrc(src);
3614      }
3615      b.addAllXAttrs(PBHelper.convertXAttrProto(xAttrs));
3616      b.build().writeDelimitedTo(out);
3617      // clientId and callId
3618      writeRpcIds(rpcClientId, rpcCallId, out);
3619    }
3620
3621    @Override
3622    protected void toXml(ContentHandler contentHandler) throws SAXException {
3623      XMLUtils.addSaxString(contentHandler, "SRC", src);
3624      appendXAttrsToXml(contentHandler, xAttrs);
3625      appendRpcIdsToXml(contentHandler, rpcClientId, rpcCallId);
3626    }
3627
3628    @Override
3629    void fromXml(Stanza st) throws InvalidXmlException {
3630      src = st.getValue("SRC");
3631      xAttrs = readXAttrsFromXml(st);
3632      readRpcIdsFromXml(st);
3633    }
3634  }
3635  
3636  static class SetXAttrOp extends FSEditLogOp {
3637    List<XAttr> xAttrs;
3638    String src;
3639    
3640    private SetXAttrOp() {
3641      super(OP_SET_XATTR);
3642    }
3643    
3644    static SetXAttrOp getInstance() {
3645      return new SetXAttrOp();
3646    }
3647
3648    @Override
3649    void readFields(DataInputStream in, int logVersion) throws IOException {
3650      XAttrEditLogProto p = XAttrEditLogProto.parseDelimitedFrom(in);
3651      src = p.getSrc();
3652      xAttrs = PBHelper.convertXAttrs(p.getXAttrsList());
3653      readRpcIds(in, logVersion);
3654    }
3655
3656    @Override
3657    public void writeFields(DataOutputStream out) throws IOException {
3658      XAttrEditLogProto.Builder b = XAttrEditLogProto.newBuilder();
3659      if (src != null) {
3660        b.setSrc(src);
3661      }
3662      b.addAllXAttrs(PBHelper.convertXAttrProto(xAttrs));
3663      b.build().writeDelimitedTo(out);
3664      // clientId and callId
3665      writeRpcIds(rpcClientId, rpcCallId, out);
3666    }
3667
3668    @Override
3669    protected void toXml(ContentHandler contentHandler) throws SAXException {
3670      XMLUtils.addSaxString(contentHandler, "SRC", src);
3671      appendXAttrsToXml(contentHandler, xAttrs);
3672      appendRpcIdsToXml(contentHandler, rpcClientId, rpcCallId);
3673    }
3674
3675    @Override
3676    void fromXml(Stanza st) throws InvalidXmlException {
3677      src = st.getValue("SRC");
3678      xAttrs = readXAttrsFromXml(st);
3679      readRpcIdsFromXml(st);
3680    }
3681  }
3682
3683  static class SetAclOp extends FSEditLogOp {
3684    List<AclEntry> aclEntries = Lists.newArrayList();
3685    String src;
3686
3687    private SetAclOp() {
3688      super(OP_SET_ACL);
3689    }
3690
3691    static SetAclOp getInstance() {
3692      return new SetAclOp();
3693    }
3694
3695    @Override
3696    void readFields(DataInputStream in, int logVersion) throws IOException {
3697      AclEditLogProto p = AclEditLogProto.parseDelimitedFrom(in);
3698      if (p == null) {
3699        throw new IOException("Failed to read fields from SetAclOp");
3700      }
3701      src = p.getSrc();
3702      aclEntries = PBHelper.convertAclEntry(p.getEntriesList());
3703    }
3704
3705    @Override
3706    public void writeFields(DataOutputStream out) throws IOException {
3707      AclEditLogProto.Builder b = AclEditLogProto.newBuilder();
3708      if (src != null)
3709        b.setSrc(src);
3710      b.addAllEntries(PBHelper.convertAclEntryProto(aclEntries));
3711      b.build().writeDelimitedTo(out);
3712    }
3713
3714    @Override
3715    protected void toXml(ContentHandler contentHandler) throws SAXException {
3716      XMLUtils.addSaxString(contentHandler, "SRC", src);
3717      appendAclEntriesToXml(contentHandler, aclEntries);
3718    }
3719
3720    @Override
3721    void fromXml(Stanza st) throws InvalidXmlException {
3722      src = st.getValue("SRC");
3723      aclEntries = readAclEntriesFromXml(st);
3724      if (aclEntries == null) {
3725        aclEntries = Lists.newArrayList();
3726      }
3727    }
3728  }
3729
3730  static private short readShort(DataInputStream in) throws IOException {
3731    return Short.parseShort(FSImageSerialization.readString(in));
3732  }
3733
3734  static private long readLong(DataInputStream in) throws IOException {
3735    return Long.parseLong(FSImageSerialization.readString(in));
3736  }
3737
3738  /**
3739   * A class to read in blocks stored in the old format. The only two
3740   * fields in the block were blockid and length.
3741   */
3742  static class BlockTwo implements Writable {
3743    long blkid;
3744    long len;
3745
3746    static {                                      // register a ctor
3747      WritableFactories.setFactory
3748        (BlockTwo.class,
3749         new WritableFactory() {
3750           @Override
3751           public Writable newInstance() { return new BlockTwo(); }
3752         });
3753    }
3754
3755
3756    BlockTwo() {
3757      blkid = 0;
3758      len = 0;
3759    }
3760    /////////////////////////////////////
3761    // Writable
3762    /////////////////////////////////////
3763    @Override
3764    public void write(DataOutput out) throws IOException {
3765      out.writeLong(blkid);
3766      out.writeLong(len);
3767    }
3768
3769    @Override
3770    public void readFields(DataInput in) throws IOException {
3771      this.blkid = in.readLong();
3772      this.len = in.readLong();
3773    }
3774  }
3775  /**
3776   * Operation corresponding to upgrade
3777   */
3778  static class RollingUpgradeOp extends FSEditLogOp { // @Idempotent
3779    private final String name;
3780    private long time;
3781
3782    public RollingUpgradeOp(FSEditLogOpCodes code, String name) {
3783      super(code);
3784      this.name = name.toUpperCase();
3785    }
3786
3787    static RollingUpgradeOp getStartInstance(OpInstanceCache cache) {
3788      return (RollingUpgradeOp) cache.get(OP_ROLLING_UPGRADE_START);
3789    }
3790
3791    static RollingUpgradeOp getFinalizeInstance(OpInstanceCache cache) {
3792      return (RollingUpgradeOp) cache.get(OP_ROLLING_UPGRADE_FINALIZE);
3793    }
3794
3795    long getTime() {
3796      return time;
3797    }
3798
3799    void setTime(long time) {
3800      this.time = time;
3801    }
3802
3803    @Override
3804    void readFields(DataInputStream in, int logVersion) throws IOException {
3805      time = in.readLong();
3806    }
3807
3808    @Override
3809    public void writeFields(DataOutputStream out) throws IOException {
3810      FSImageSerialization.writeLong(time, out);
3811    }
3812
3813    @Override
3814    protected void toXml(ContentHandler contentHandler) throws SAXException {
3815      XMLUtils.addSaxString(contentHandler, name + "TIME",
3816          Long.toString(time));
3817    }
3818
3819    @Override
3820    void fromXml(Stanza st) throws InvalidXmlException {
3821      this.time = Long.parseLong(st.getValue(name + "TIME"));
3822    }
3823
3824    @Override
3825    public String toString() {
3826      return new StringBuilder().append("RollingUpgradeOp [").append(name)
3827          .append(", time=").append(time).append("]").toString();
3828    }
3829    
3830    static class RollbackException extends IOException {
3831      private static final long serialVersionUID = 1L;
3832    }
3833  }
3834
3835  /** {@literal @Idempotent} for {@link ClientProtocol#setStoragePolicy} */
3836  static class SetStoragePolicyOp extends FSEditLogOp {
3837    String path;
3838    byte policyId;
3839
3840    private SetStoragePolicyOp() {
3841      super(OP_SET_STORAGE_POLICY);
3842    }
3843
3844    static SetStoragePolicyOp getInstance(OpInstanceCache cache) {
3845      return (SetStoragePolicyOp) cache.get(OP_SET_STORAGE_POLICY);
3846    }
3847
3848    SetStoragePolicyOp setPath(String path) {
3849      this.path = path;
3850      return this;
3851    }
3852
3853    SetStoragePolicyOp setPolicyId(byte policyId) {
3854      this.policyId = policyId;
3855      return this;
3856    }
3857
3858    @Override
3859    public void writeFields(DataOutputStream out) throws IOException {
3860      FSImageSerialization.writeString(path, out);
3861      out.writeByte(policyId);
3862    }
3863
3864    @Override
3865    void readFields(DataInputStream in, int logVersion)
3866        throws IOException {
3867      this.path = FSImageSerialization.readString(in);
3868      this.policyId = in.readByte();
3869    }
3870
3871    @Override
3872    public String toString() {
3873      StringBuilder builder = new StringBuilder();
3874      builder.append("SetStoragePolicyOp [path=");
3875      builder.append(path);
3876      builder.append(", policyId=");
3877      builder.append(policyId);
3878      builder.append(", opCode=");
3879      builder.append(opCode);
3880      builder.append(", txid=");
3881      builder.append(txid);
3882      builder.append("]");
3883      return builder.toString();
3884    }
3885
3886    @Override
3887    protected void toXml(ContentHandler contentHandler) throws SAXException {
3888      XMLUtils.addSaxString(contentHandler, "PATH", path);
3889      XMLUtils.addSaxString(contentHandler, "POLICYID",
3890          Byte.valueOf(policyId).toString());
3891    }
3892
3893    @Override
3894    void fromXml(Stanza st) throws InvalidXmlException {
3895      this.path = st.getValue("PATH");
3896      this.policyId = Byte.valueOf(st.getValue("POLICYID"));
3897    }
3898  }  
3899
3900  /**
3901   * Class for writing editlog ops
3902   */
3903  public static class Writer {
3904    private final DataOutputBuffer buf;
3905    private final Checksum checksum;
3906
3907    public Writer(DataOutputBuffer out) {
3908      this.buf = out;
3909      this.checksum = DataChecksum.newCrc32();
3910    }
3911
3912    /**
3913     * Write an operation to the output stream
3914     * 
3915     * @param op The operation to write
3916     * @throws IOException if an error occurs during writing.
3917     */
3918    public void writeOp(FSEditLogOp op) throws IOException {
3919      int start = buf.getLength();
3920      // write the op code first to make padding and terminator verification
3921      // work
3922      buf.writeByte(op.opCode.getOpCode());
3923      buf.writeInt(0); // write 0 for the length first
3924      buf.writeLong(op.txid);
3925      op.writeFields(buf);
3926      int end = buf.getLength();
3927      
3928      // write the length back: content of the op + 4 bytes checksum - op_code
3929      int length = end - start - 1;
3930      buf.writeInt(length, start + 1);
3931
3932      checksum.reset();
3933      checksum.update(buf.getData(), start, end-start);
3934      int sum = (int)checksum.getValue();
3935      buf.writeInt(sum);
3936    }
3937  }
3938
3939  /**
3940   * Class for reading editlog ops from a stream
3941   */
3942  public static class Reader {
3943    private final DataInputStream in;
3944    private final StreamLimiter limiter;
3945    private final int logVersion;
3946    private final Checksum checksum;
3947    private final OpInstanceCache cache;
3948    private int maxOpSize;
3949    private final boolean supportEditLogLength;
3950
3951    /**
3952     * Construct the reader
3953     * @param in The stream to read from.
3954     * @param logVersion The version of the data coming from the stream.
3955     */
3956    public Reader(DataInputStream in, StreamLimiter limiter, int logVersion) {
3957      this.logVersion = logVersion;
3958      if (NameNodeLayoutVersion.supports(
3959          LayoutVersion.Feature.EDITS_CHESKUM, logVersion)) {
3960        this.checksum = DataChecksum.newCrc32();
3961      } else {
3962        this.checksum = null;
3963      }
3964      // It is possible that the logVersion is actually a future layoutversion
3965      // during the rolling upgrade (e.g., the NN gets upgraded first). We
3966      // assume future layout will also support length of editlog op.
3967      this.supportEditLogLength = NameNodeLayoutVersion.supports(
3968          NameNodeLayoutVersion.Feature.EDITLOG_LENGTH, logVersion)
3969          || logVersion < NameNodeLayoutVersion.CURRENT_LAYOUT_VERSION;
3970
3971      if (this.checksum != null) {
3972        this.in = new DataInputStream(
3973            new CheckedInputStream(in, this.checksum));
3974      } else {
3975        this.in = in;
3976      }
3977      this.limiter = limiter;
3978      this.cache = new OpInstanceCache();
3979      this.maxOpSize = DFSConfigKeys.DFS_NAMENODE_MAX_OP_SIZE_DEFAULT;
3980    }
3981
3982    public void setMaxOpSize(int maxOpSize) {
3983      this.maxOpSize = maxOpSize;
3984    }
3985
3986    /**
3987     * Read an operation from the input stream.
3988     * 
3989     * Note that the objects returned from this method may be re-used by future
3990     * calls to the same method.
3991     * 
3992     * @param skipBrokenEdits    If true, attempt to skip over damaged parts of
3993     * the input stream, rather than throwing an IOException
3994     * @return the operation read from the stream, or null at the end of the 
3995     *         file
3996     * @throws IOException on error.  This function should only throw an
3997     *         exception when skipBrokenEdits is false.
3998     */
3999    public FSEditLogOp readOp(boolean skipBrokenEdits) throws IOException {
4000      while (true) {
4001        try {
4002          return decodeOp();
4003        } catch (IOException e) {
4004          in.reset();
4005          if (!skipBrokenEdits) {
4006            throw e;
4007          }
4008        } catch (RuntimeException e) {
4009          // FSEditLogOp#decodeOp is not supposed to throw RuntimeException.
4010          // However, we handle it here for recovery mode, just to be more
4011          // robust.
4012          in.reset();
4013          if (!skipBrokenEdits) {
4014            throw e;
4015          }
4016        } catch (Throwable e) {
4017          in.reset();
4018          if (!skipBrokenEdits) {
4019            throw new IOException("got unexpected exception " +
4020                e.getMessage(), e);
4021          }
4022        }
4023        // Move ahead one byte and re-try the decode process.
4024        if (in.skip(1) < 1) {
4025          return null;
4026        }
4027      }
4028    }
4029
4030    private void verifyTerminator() throws IOException {
4031      /** The end of the edit log should contain only 0x00 or 0xff bytes.
4032       * If it contains other bytes, the log itself may be corrupt.
4033       * It is important to check this; if we don't, a stray OP_INVALID byte 
4034       * could make us stop reading the edit log halfway through, and we'd never
4035       * know that we had lost data.
4036       */
4037      byte[] buf = new byte[4096];
4038      limiter.clearLimit();
4039      int numRead = -1, idx = 0;
4040      while (true) {
4041        try {
4042          numRead = -1;
4043          idx = 0;
4044          numRead = in.read(buf);
4045          if (numRead == -1) {
4046            return;
4047          }
4048          while (idx < numRead) {
4049            if ((buf[idx] != (byte)0) && (buf[idx] != (byte)-1)) {
4050              throw new IOException("Read extra bytes after " +
4051                "the terminator!");
4052            }
4053            idx++;
4054          }
4055        } finally {
4056          // After reading each group of bytes, we reposition the mark one
4057          // byte before the next group.  Similarly, if there is an error, we
4058          // want to reposition the mark one byte before the error
4059          if (numRead != -1) { 
4060            in.reset();
4061            IOUtils.skipFully(in, idx);
4062            in.mark(buf.length + 1);
4063            IOUtils.skipFully(in, 1);
4064          }
4065        }
4066      }
4067    }
4068
4069    /**
4070     * Read an opcode from the input stream.
4071     *
4072     * @return   the opcode, or null on EOF.
4073     *
4074     * If an exception is thrown, the stream's mark will be set to the first
4075     * problematic byte.  This usually means the beginning of the opcode.
4076     */
4077    private FSEditLogOp decodeOp() throws IOException {
4078      limiter.setLimit(maxOpSize);
4079      in.mark(maxOpSize);
4080
4081      if (checksum != null) {
4082        checksum.reset();
4083      }
4084
4085      byte opCodeByte;
4086      try {
4087        opCodeByte = in.readByte();
4088      } catch (EOFException eof) {
4089        // EOF at an opcode boundary is expected.
4090        return null;
4091      }
4092
4093      FSEditLogOpCodes opCode = FSEditLogOpCodes.fromByte(opCodeByte);
4094      if (opCode == OP_INVALID) {
4095        verifyTerminator();
4096        return null;
4097      }
4098
4099      FSEditLogOp op = cache.get(opCode);
4100      if (op == null) {
4101        throw new IOException("Read invalid opcode " + opCode);
4102      }
4103
4104      if (supportEditLogLength) {
4105        in.readInt();
4106      }
4107
4108      if (NameNodeLayoutVersion.supports(
4109          LayoutVersion.Feature.STORED_TXIDS, logVersion)) {
4110        // Read the txid
4111        op.setTransactionId(in.readLong());
4112      } else {
4113        op.setTransactionId(HdfsConstants.INVALID_TXID);
4114      }
4115
4116      op.readFields(in, logVersion);
4117
4118      validateChecksum(in, checksum, op.txid);
4119      return op;
4120    }
4121
4122    /**
4123     * Similar with decodeOp(), but instead of doing the real decoding, we skip
4124     * the content of the op if the length of the editlog is supported.
4125     * @return the last txid of the segment, or INVALID_TXID on exception
4126     */
4127    public long scanOp() throws IOException {
4128      if (supportEditLogLength) {
4129        limiter.setLimit(maxOpSize);
4130        in.mark(maxOpSize);
4131
4132        final byte opCodeByte;
4133        try {
4134          opCodeByte = in.readByte(); // op code
4135        } catch (EOFException e) {
4136          return HdfsConstants.INVALID_TXID;
4137        }
4138
4139        FSEditLogOpCodes opCode = FSEditLogOpCodes.fromByte(opCodeByte);
4140        if (opCode == OP_INVALID) {
4141          verifyTerminator();
4142          return HdfsConstants.INVALID_TXID;
4143        }
4144
4145        int length = in.readInt(); // read the length of the op
4146        long txid = in.readLong(); // read the txid
4147
4148        // skip the remaining content
4149        IOUtils.skipFully(in, length - 8); 
4150        // TODO: do we want to verify checksum for JN? For now we don't.
4151        return txid;
4152      } else {
4153        FSEditLogOp op = decodeOp();
4154        return op == null ? HdfsConstants.INVALID_TXID : op.getTransactionId();
4155      }
4156    }
4157
4158    /**
4159     * Validate a transaction's checksum
4160     */
4161    private void validateChecksum(DataInputStream in,
4162                                  Checksum checksum,
4163                                  long txid)
4164        throws IOException {
4165      if (checksum != null) {
4166        int calculatedChecksum = (int)checksum.getValue();
4167        int readChecksum = in.readInt(); // read in checksum
4168        if (readChecksum != calculatedChecksum) {
4169          throw new ChecksumException(
4170              "Transaction is corrupt. Calculated checksum is " +
4171              calculatedChecksum + " but read checksum " + readChecksum, txid);
4172        }
4173      }
4174    }
4175  }
4176
4177  public void outputToXml(ContentHandler contentHandler) throws SAXException {
4178    contentHandler.startElement("", "", "RECORD", new AttributesImpl());
4179    XMLUtils.addSaxString(contentHandler, "OPCODE", opCode.toString());
4180    contentHandler.startElement("", "", "DATA", new AttributesImpl());
4181    XMLUtils.addSaxString(contentHandler, "TXID", "" + txid);
4182    toXml(contentHandler);
4183    contentHandler.endElement("", "", "DATA");
4184    contentHandler.endElement("", "", "RECORD");
4185  }
4186
4187  protected abstract void toXml(ContentHandler contentHandler)
4188      throws SAXException;
4189  
4190  abstract void fromXml(Stanza st) throws InvalidXmlException;
4191  
4192  public void decodeXml(Stanza st) throws InvalidXmlException {
4193    this.txid = Long.parseLong(st.getValue("TXID"));
4194    fromXml(st);
4195  }
4196  
4197  public static void blockToXml(ContentHandler contentHandler, Block block) 
4198      throws SAXException {
4199    contentHandler.startElement("", "", "BLOCK", new AttributesImpl());
4200    XMLUtils.addSaxString(contentHandler, "BLOCK_ID",
4201        Long.toString(block.getBlockId()));
4202    XMLUtils.addSaxString(contentHandler, "NUM_BYTES",
4203        Long.toString(block.getNumBytes()));
4204    XMLUtils.addSaxString(contentHandler, "GENSTAMP",
4205        Long.toString(block.getGenerationStamp()));
4206    contentHandler.endElement("", "", "BLOCK");
4207  }
4208
4209  public static Block blockFromXml(Stanza st)
4210      throws InvalidXmlException {
4211    long blockId = Long.parseLong(st.getValue("BLOCK_ID"));
4212    long numBytes = Long.parseLong(st.getValue("NUM_BYTES"));
4213    long generationStamp = Long.parseLong(st.getValue("GENSTAMP"));
4214    return new Block(blockId, numBytes, generationStamp);
4215  }
4216
4217  public static void delegationTokenToXml(ContentHandler contentHandler,
4218      DelegationTokenIdentifier token) throws SAXException {
4219    contentHandler.startElement("", "", "DELEGATION_TOKEN_IDENTIFIER", new AttributesImpl());
4220    XMLUtils.addSaxString(contentHandler, "KIND", token.getKind().toString());
4221    XMLUtils.addSaxString(contentHandler, "SEQUENCE_NUMBER",
4222        Integer.toString(token.getSequenceNumber()));
4223    XMLUtils.addSaxString(contentHandler, "OWNER",
4224        token.getOwner().toString());
4225    XMLUtils.addSaxString(contentHandler, "RENEWER",
4226        token.getRenewer().toString());
4227    XMLUtils.addSaxString(contentHandler, "REALUSER",
4228        token.getRealUser().toString());
4229    XMLUtils.addSaxString(contentHandler, "ISSUE_DATE",
4230        Long.toString(token.getIssueDate()));
4231    XMLUtils.addSaxString(contentHandler, "MAX_DATE",
4232        Long.toString(token.getMaxDate()));
4233    XMLUtils.addSaxString(contentHandler, "MASTER_KEY_ID",
4234        Integer.toString(token.getMasterKeyId()));
4235    contentHandler.endElement("", "", "DELEGATION_TOKEN_IDENTIFIER");
4236  }
4237
4238  public static DelegationTokenIdentifier delegationTokenFromXml(Stanza st)
4239      throws InvalidXmlException {
4240    String kind = st.getValue("KIND");
4241    if (!kind.equals(DelegationTokenIdentifier.
4242        HDFS_DELEGATION_KIND.toString())) {
4243      throw new InvalidXmlException("can't understand " +
4244        "DelegationTokenIdentifier KIND " + kind);
4245    }
4246    int seqNum = Integer.parseInt(st.getValue("SEQUENCE_NUMBER"));
4247    String owner = st.getValue("OWNER");
4248    String renewer = st.getValue("RENEWER");
4249    String realuser = st.getValue("REALUSER");
4250    long issueDate = Long.parseLong(st.getValue("ISSUE_DATE"));
4251    long maxDate = Long.parseLong(st.getValue("MAX_DATE"));
4252    int masterKeyId = Integer.parseInt(st.getValue("MASTER_KEY_ID"));
4253    DelegationTokenIdentifier token =
4254        new DelegationTokenIdentifier(new Text(owner),
4255            new Text(renewer), new Text(realuser));
4256    token.setSequenceNumber(seqNum);
4257    token.setIssueDate(issueDate);
4258    token.setMaxDate(maxDate);
4259    token.setMasterKeyId(masterKeyId);
4260    return token;
4261  }
4262
4263  public static void delegationKeyToXml(ContentHandler contentHandler,
4264      DelegationKey key) throws SAXException {
4265    contentHandler.startElement("", "", "DELEGATION_KEY", new AttributesImpl());
4266    XMLUtils.addSaxString(contentHandler, "KEY_ID",
4267        Integer.toString(key.getKeyId()));
4268    XMLUtils.addSaxString(contentHandler, "EXPIRY_DATE",
4269        Long.toString(key.getExpiryDate()));
4270    if (key.getEncodedKey() != null) {
4271      XMLUtils.addSaxString(contentHandler, "KEY",
4272          Hex.encodeHexString(key.getEncodedKey()));
4273    }
4274    contentHandler.endElement("", "", "DELEGATION_KEY");
4275  }
4276  
4277  public static DelegationKey delegationKeyFromXml(Stanza st)
4278      throws InvalidXmlException {
4279    int keyId = Integer.parseInt(st.getValue("KEY_ID"));
4280    long expiryDate = Long.parseLong(st.getValue("EXPIRY_DATE"));
4281    byte key[] = null;
4282    try {
4283      key = Hex.decodeHex(st.getValue("KEY").toCharArray());
4284    } catch (DecoderException e) {
4285      throw new InvalidXmlException(e.toString());
4286    } catch (InvalidXmlException e) {
4287    }
4288    return new DelegationKey(keyId, expiryDate, key);
4289  }
4290
4291  public static void permissionStatusToXml(ContentHandler contentHandler,
4292      PermissionStatus perm) throws SAXException {
4293    contentHandler.startElement("", "", "PERMISSION_STATUS", new AttributesImpl());
4294    XMLUtils.addSaxString(contentHandler, "USERNAME", perm.getUserName());
4295    XMLUtils.addSaxString(contentHandler, "GROUPNAME", perm.getGroupName());
4296    fsPermissionToXml(contentHandler, perm.getPermission());
4297    contentHandler.endElement("", "", "PERMISSION_STATUS");
4298  }
4299
4300  public static PermissionStatus permissionStatusFromXml(Stanza st)
4301      throws InvalidXmlException {
4302    Stanza status = st.getChildren("PERMISSION_STATUS").get(0);
4303    String username = status.getValue("USERNAME");
4304    String groupname = status.getValue("GROUPNAME");
4305    FsPermission mode = fsPermissionFromXml(status);
4306    return new PermissionStatus(username, groupname, mode);
4307  }
4308
4309  public static void fsPermissionToXml(ContentHandler contentHandler,
4310      FsPermission mode) throws SAXException {
4311    XMLUtils.addSaxString(contentHandler, "MODE", Short.valueOf(mode.toShort())
4312        .toString());
4313  }
4314
4315  public static FsPermission fsPermissionFromXml(Stanza st)
4316      throws InvalidXmlException {
4317    short mode = Short.valueOf(st.getValue("MODE"));
4318    return new FsPermission(mode);
4319  }
4320
4321  private static void fsActionToXml(ContentHandler contentHandler, FsAction v)
4322      throws SAXException {
4323    XMLUtils.addSaxString(contentHandler, "PERM", v.SYMBOL);
4324  }
4325
4326  private static FsAction fsActionFromXml(Stanza st) throws InvalidXmlException {
4327    FsAction v = FSACTION_SYMBOL_MAP.get(st.getValue("PERM"));
4328    if (v == null)
4329      throw new InvalidXmlException("Invalid value for FsAction");
4330    return v;
4331  }
4332
4333  private static void appendAclEntriesToXml(ContentHandler contentHandler,
4334      List<AclEntry> aclEntries) throws SAXException {
4335    for (AclEntry e : aclEntries) {
4336      contentHandler.startElement("", "", "ENTRY", new AttributesImpl());
4337      XMLUtils.addSaxString(contentHandler, "SCOPE", e.getScope().name());
4338      XMLUtils.addSaxString(contentHandler, "TYPE", e.getType().name());
4339      if (e.getName() != null) {
4340        XMLUtils.addSaxString(contentHandler, "NAME", e.getName());
4341      }
4342      fsActionToXml(contentHandler, e.getPermission());
4343      contentHandler.endElement("", "", "ENTRY");
4344    }
4345  }
4346
4347  private static List<AclEntry> readAclEntriesFromXml(Stanza st) {
4348    List<AclEntry> aclEntries = Lists.newArrayList();
4349    if (!st.hasChildren("ENTRY"))
4350      return null;
4351
4352    List<Stanza> stanzas = st.getChildren("ENTRY");
4353    for (Stanza s : stanzas) {
4354      AclEntry e = new AclEntry.Builder()
4355        .setScope(AclEntryScope.valueOf(s.getValue("SCOPE")))
4356        .setType(AclEntryType.valueOf(s.getValue("TYPE")))
4357        .setName(s.getValueOrNull("NAME"))
4358        .setPermission(fsActionFromXml(s)).build();
4359      aclEntries.add(e);
4360    }
4361    return aclEntries;
4362  }
4363
4364  private static void appendXAttrsToXml(ContentHandler contentHandler,
4365      List<XAttr> xAttrs) throws SAXException {
4366    for (XAttr xAttr: xAttrs) {
4367      contentHandler.startElement("", "", "XATTR", new AttributesImpl());
4368      XMLUtils.addSaxString(contentHandler, "NAMESPACE",
4369          xAttr.getNameSpace().toString());
4370      XMLUtils.addSaxString(contentHandler, "NAME", xAttr.getName());
4371      if (xAttr.getValue() != null) {
4372        try {
4373          XMLUtils.addSaxString(contentHandler, "VALUE",
4374              XAttrCodec.encodeValue(xAttr.getValue(), XAttrCodec.HEX));
4375        } catch (IOException e) {
4376          throw new SAXException(e);
4377        }
4378      }
4379      contentHandler.endElement("", "", "XATTR");
4380    }
4381  }
4382
4383  private static List<XAttr> readXAttrsFromXml(Stanza st)
4384      throws InvalidXmlException {
4385    if (!st.hasChildren("XATTR")) {
4386      return null;
4387    }
4388
4389    List<Stanza> stanzas = st.getChildren("XATTR");
4390    List<XAttr> xattrs = Lists.newArrayListWithCapacity(stanzas.size());
4391    for (Stanza a: stanzas) {
4392      XAttr.Builder builder = new XAttr.Builder();
4393      builder.setNameSpace(XAttr.NameSpace.valueOf(a.getValue("NAMESPACE"))).
4394          setName(a.getValue("NAME"));
4395      String v = a.getValueOrNull("VALUE");
4396      if (v != null) {
4397        try {
4398          builder.setValue(XAttrCodec.decodeValue(v));
4399        } catch (IOException e) {
4400          throw new InvalidXmlException(e.toString());
4401        }
4402      }
4403      xattrs.add(builder.build());
4404    }
4405    return xattrs;
4406  }
4407}