/* This code is part of Freenet. It is distributed under the GNU General * Public License, version 2 (or at your option any later version). See * http://www.gnu.org/ for further details of the GPL. */ package freenet.clients.fcp; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.Serializable; import java.net.MalformedURLException; import freenet.client.FailureCodeTracker; import freenet.client.FetchException; import freenet.client.FetchException.FetchExceptionMode; import freenet.keys.FreenetURI; import freenet.node.Node; import freenet.support.LogThresholdCallback; import freenet.support.Logger; import freenet.support.Logger.LogLevel; import freenet.support.io.StorageFormatException; import freenet.support.SimpleFieldSet; public class GetFailedMessage extends FCPMessage implements Serializable { private static final long serialVersionUID = 1L; final FetchExceptionMode code; final String extraDescription; final FailureCodeTracker tracker; final boolean isFatal; final String identifier; final boolean global; final long expectedDataLength; final String expectedMimeType; final boolean finalizedExpected; final FreenetURI redirectURI; private static volatile boolean logMINOR; static { Logger.registerLogThresholdCallback(new LogThresholdCallback(){ @Override public void shouldUpdate(){ logMINOR = Logger.shouldLog(LogLevel.MINOR, this); } }); } public GetFailedMessage(FetchException e, String identifier, boolean global) { if(logMINOR) Logger.minor(this, "Creating get failed from "+e+" for "+identifier, e); this.tracker = e.errorCodes; this.code = e.mode; this.extraDescription = e.extraMessage; this.isFatal = e.isFatal(); this.identifier = identifier; this.global = global; this.expectedDataLength = e.expectedSize; this.expectedMimeType = e.getExpectedMimeType(); this.finalizedExpected = e.finalizedSize(); this.redirectURI = e.newURI; } /** * Construct from a fieldset. Used in serialization of persistent requests. * Will need to be made more tolerant of syntax errors if is used in an FCP * client library. FIXME. * @param useVerboseFields If true, read in verbose fields (CodeDescription * etc), if false, reconstruct them from the error code. */ public GetFailedMessage(SimpleFieldSet fs, boolean useVerboseFields) throws MalformedURLException { identifier = fs.get("Identifier"); if(identifier == null) throw new NullPointerException(); code = FetchExceptionMode.getByCode(Integer.parseInt(fs.get("Code"))); if(useVerboseFields) { isFatal = fs.getBoolean("Fatal", false); } else { isFatal = FetchException.isFatal(code); } extraDescription = fs.get("ExtraDescription"); SimpleFieldSet trackerSubset = fs.subset("Errors"); if(trackerSubset != null) { tracker = new FailureCodeTracker(true, trackerSubset); } else { tracker = null; } expectedMimeType = fs.get("ExpectedMimeType"); finalizedExpected = fs.getBoolean("FinalizedExpected", false); String s = fs.get("ExpectedDataLength"); if(s != null) { expectedDataLength = Long.parseLong(s); } else expectedDataLength = -1; s = fs.get("RedirectURI"); if(s != null) this.redirectURI = new FreenetURI(s); else this.redirectURI = null; this.global = fs.getBoolean("Global", false); } protected GetFailedMessage() { // For serialization. code = null; extraDescription = null; tracker = null; isFatal = false; identifier = null; global = false; expectedDataLength = 0; expectedMimeType = null; finalizedExpected = false; redirectURI = null; } @Override public SimpleFieldSet getFieldSet() { return getFieldSet(true); } /** * Write to a SimpleFieldSet for storage or transmission. * @param verbose If true, include fields which derive directly from static * stuff on InsertException (and therefore can be omitted if talking to self * or another node). */ public SimpleFieldSet getFieldSet(boolean verbose) { SimpleFieldSet sfs = new SimpleFieldSet(true); sfs.put("Code", code.code); if(verbose) sfs.putSingle("CodeDescription", getFailedMessage()); if(extraDescription != null) sfs.putSingle("ExtraDescription", extraDescription); if(verbose) sfs.put("Fatal", isFatal); if(tracker != null) { sfs.tput("Errors", tracker.toFieldSet(verbose)); } if(verbose) sfs.putSingle("ShortCodeDescription", getShortFailedMessage()); sfs.putSingle("Identifier", identifier); if(expectedDataLength > -1) { sfs.put("ExpectedDataLength", expectedDataLength); } if(expectedMimeType != null) sfs.putSingle("ExpectedMetadata.ContentType", expectedMimeType); if(finalizedExpected) sfs.putSingle("FinalizedExpected", "true"); if(redirectURI != null) sfs.putSingle("RedirectURI", redirectURI.toString(false, false)); return sfs; } @Override public String getName() { return "GetFailed"; } @Override public void run(FCPConnectionHandler handler, Node node) throws MessageInvalidException { throw new MessageInvalidException(ProtocolErrorMessage.INVALID_MESSAGE, "GetFailed goes from server to client not the other way around", identifier, global); } public String getFailedMessage() { return FetchException.getMessage(code); } public String getShortFailedMessage() { return FetchException.getShortMessage(code); } public String getLongFailedMessage() { if(extraDescription != null) return getFailedMessage() + ": " + extraDescription; else return getFailedMessage(); } static final int VERSION = 1; public void writeTo(DataOutputStream dos) throws IOException { dos.writeInt(VERSION); // Do not write anything redundant. dos.writeInt(code.code); writePossiblyNull(extraDescription, dos); dos.writeBoolean(finalizedExpected); writePossiblyNull(redirectURI == null ? null : redirectURI.toString(), dos); } public GetFailedMessage(DataInputStream dis, RequestIdentifier reqID, long expectedSize, String expectedType) throws StorageFormatException, IOException { int version = dis.readInt(); if(version != VERSION) throw new StorageFormatException("Bad version in GetFailedMessage"); int x = dis.readInt(); try { code = FetchExceptionMode.getByCode(x); } catch (IllegalArgumentException e) { throw new StorageFormatException("Bad error code"); } this.isFatal = FetchException.isFatal(code); this.extraDescription = readPossiblyNull(dis); this.finalizedExpected = dis.readBoolean(); String s = readPossiblyNull(dis); if(s != null) { try { redirectURI = new FreenetURI(s); } catch (MalformedURLException e) { throw new StorageFormatException("Bad redirect URI in GetFailedMessage: "+e); } } else { redirectURI = null; } this.global = reqID.globalQueue; this.identifier = reqID.identifier; this.tracker = null; // Don't save that level of detail. this.expectedDataLength = expectedSize; this.expectedMimeType = expectedType; } private String readPossiblyNull(DataInputStream dis) throws IOException { if(dis.readBoolean()) { return dis.readUTF(); } else { return null; } } private void writePossiblyNull(String s, DataOutputStream dos) throws IOException { if(s != null) { dos.writeBoolean(true); dos.writeUTF(s); } else { dos.writeBoolean(false); } } }