/* 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.client; import java.util.HashMap; import freenet.client.async.TooManyFilesInsertException; import freenet.keys.FreenetURI; import freenet.l10n.NodeL10n; import freenet.node.LowLevelPutException; import freenet.support.LogThresholdCallback; import freenet.support.Logger; import freenet.support.Logger.LogLevel; /** * Thrown when a high-level insert fails. For most failures, there will not be a stack trace, or it * will be inaccurate. */ public class InsertException extends Exception implements Cloneable { private static final long serialVersionUID = -1106716067841151962L; /** Failure mode, see the constants below. */ public final InsertExceptionMode mode; /** Collect errors when there are multiple failures. The error mode will be FATAL_ERRORS_IN_BLOCKS or * TOO_MANY_RETRIES_IN_BLOCKS i.e. a splitfile failed. */ public FailureCodeTracker errorCodes; /** If a non-serious error, the URI we expect the insert to go to if it had succeeded. */ public FreenetURI uri; /** Extra detail message */ public final String extra; /** Get the failure mode. */ public InsertExceptionMode getMode() { return mode; } private static volatile boolean logMINOR; static { Logger.registerLogThresholdCallback(new LogThresholdCallback() { @Override public void shouldUpdate() { logMINOR = Logger.shouldLog(LogLevel.MINOR, this); } }); } /** * zero arg c'tor for db4o on jamvm */ @SuppressWarnings("unused") private InsertException() { mode = null; extra = null; } public InsertException(InsertExceptionMode m, String msg, FreenetURI expectedURI) { super(getMessage(m)+": "+msg); extra = msg; mode = m; errorCodes = null; this.uri = expectedURI; if(mode == InsertExceptionMode.INTERNAL_ERROR) Logger.error(this, "Internal error: "+this); else if(logMINOR) Logger.minor(this, "Creating InsertException: "+getMessage(mode)+": "+msg, this); } public InsertException(InsertExceptionMode m, FreenetURI expectedURI) { super(getMessage(m)); extra = null; mode = m; errorCodes = null; this.uri = expectedURI; if(mode == InsertExceptionMode.INTERNAL_ERROR) Logger.error(this, "Internal error: "+this); else if(logMINOR) Logger.minor(this, "Creating InsertException: "+getMessage(mode), this); } public InsertException(InsertExceptionMode mode, Throwable e, FreenetURI expectedURI) { super(getMessage(mode)+": "+e.getMessage()); extra = e.getMessage(); this.mode = mode; errorCodes = null; initCause(e); this.uri = expectedURI; if(mode == InsertExceptionMode.INTERNAL_ERROR) Logger.error(this, "Internal error: "+this); else if(logMINOR) Logger.minor(this, "Creating InsertException: "+getMessage(mode)+": "+e, this); } public InsertException(InsertExceptionMode mode, String message, Throwable e, FreenetURI expectedURI) { super(getMessage(mode)+": "+message+": "+e.getMessage()); extra = e.getMessage(); this.mode = mode; errorCodes = null; initCause(e); this.uri = expectedURI; if(mode == InsertExceptionMode.INTERNAL_ERROR) Logger.error(this, "Internal error: "+this); else if(logMINOR) Logger.minor(this, "Creating InsertException: "+getMessage(mode)+": "+e, this); } public InsertException(InsertExceptionMode mode, FailureCodeTracker errorCodes, FreenetURI expectedURI) { super(getMessage(mode)); extra = null; this.mode = mode; this.errorCodes = errorCodes; this.uri = expectedURI; if(mode == InsertExceptionMode.INTERNAL_ERROR) Logger.error(this, "Internal error: "+this); else if(logMINOR) Logger.minor(this, "Creating InsertException: "+getMessage(mode), this); } public InsertException(InsertExceptionMode mode, String message, FailureCodeTracker errorCodes, FreenetURI expectedURI) { super(message == null ? getMessage(mode) : (getMessage(mode)+": "+message)); extra = message; this.mode = mode; this.errorCodes = errorCodes; this.uri = expectedURI; if(mode == InsertExceptionMode.INTERNAL_ERROR) Logger.error(this, "Internal error: "+this); else if(logMINOR) Logger.minor(this, "Creating InsertException: "+getMessage(mode), this); } public InsertException(InsertExceptionMode mode) { super(getMessage(mode)); extra = null; this.mode = mode; this.errorCodes = null; this.uri = null; if(mode == InsertExceptionMode.INTERNAL_ERROR) Logger.error(this, "Internal error: "+this); else if(logMINOR) Logger.minor(this, "Creating InsertException: "+getMessage(mode), this); } public InsertException(InsertException e) { super(e.getMessage()); extra = e.extra; mode = e.mode; errorCodes = e.errorCodes == null ? null : e.errorCodes.clone(); uri = e.uri; } public InsertException(TooManyFilesInsertException e) { this(InsertExceptionMode.TOO_MANY_FILES, (String)null, null); } public static InsertException constructFrom(LowLevelPutException e) { switch(e.code) { case LowLevelPutException.COLLISION: return new InsertException(InsertExceptionMode.COLLISION); case LowLevelPutException.INTERNAL_ERROR: return new InsertException(InsertExceptionMode.INTERNAL_ERROR); case LowLevelPutException.REJECTED_OVERLOAD: return new InsertException(InsertExceptionMode.REJECTED_OVERLOAD); case LowLevelPutException.ROUTE_NOT_FOUND: return new InsertException(InsertExceptionMode.ROUTE_NOT_FOUND); case LowLevelPutException.ROUTE_REALLY_NOT_FOUND: return new InsertException(InsertExceptionMode.ROUTE_REALLY_NOT_FOUND); default: Logger.error(InsertException.class, "Unknown LowLevelPutException: "+e+" code "+e.code, new Exception("error")); return new InsertException(InsertExceptionMode.INTERNAL_ERROR, "Unknown error "+e.code, null); } } private static final HashMap<Integer, InsertExceptionMode> modes = new HashMap<Integer, InsertExceptionMode>(); public static enum InsertExceptionMode { /** Caller supplied a URI we cannot use */ INVALID_URI(1), /** Failed to read from or write to a bucket; a kind of internal error */ BUCKET_ERROR(2), /** Internal error of some sort */ INTERNAL_ERROR(3), /** Downstream node was overloaded */ REJECTED_OVERLOAD(4), /** Couldn't find enough nodes to send the data to */ ROUTE_NOT_FOUND(5), /** There were fatal errors in a splitfile insert. */ FATAL_ERRORS_IN_BLOCKS(6), /** Could not insert a splitfile because a block failed too many times */ TOO_MANY_RETRIES_IN_BLOCKS(7), /** Not able to leave the node at all */ ROUTE_REALLY_NOT_FOUND(8), /** Collided with pre-existing content */ COLLISION(9), /** Cancelled by user */ CANCELLED(10), /** Meta string used in the key (most probably '/') */ META_STRINGS_NOT_SUPPORTED(11), /** Invalid binary blob data supplied so cannot insert it */ BINARY_BLOB_FORMAT_ERROR(12), /** Too many files in a directory in a site insert */ TOO_MANY_FILES(13), /** File being uploaded is bigger than maximum supported size */ TOO_BIG(14); public final int code; InsertExceptionMode(int code) { this.code = code; if(code < 0 || code >= UPPER_LIMIT_ERROR_CODE) throw new IllegalArgumentException(); if(modes.containsKey(code)) throw new IllegalArgumentException(); modes.put(code, this); } public static InsertExceptionMode getByCode(int code) { if(modes.get(code) == null) throw new IllegalArgumentException(); return modes.get(code); } } /** There will never be more error codes than this constant. Must not change, used for some * data structures. */ public static final int UPPER_LIMIT_ERROR_CODE = 1024; /** Get the (localised) short name of this failure mode. */ public static String getMessage(InsertExceptionMode mode) { // FIXME change the l10n to use the keyword not the code String ret = NodeL10n.getBase().getString("InsertException.longError."+mode.code); if(ret == null) return "Unknown error "+mode; else return ret; } /** Get the (localised) long explanation for this failure mode. */ public static String getShortMessage(InsertExceptionMode mode) { // FIXME change the l10n to use the keyword not the code String ret = NodeL10n.getBase().getString("InsertException.shortError."+mode.code); if(ret == null) return "Unknown error "+mode; else return ret; } /** Is this error fatal? Non-fatal errors are errors which are likely to go away with * more retries, or at least for which there is some point retrying. */ public boolean isFatal() { return isFatal(mode); } public static boolean isFatal(InsertExceptionMode mode) { switch(mode) { case INVALID_URI: case FATAL_ERRORS_IN_BLOCKS: case COLLISION: case CANCELLED: case META_STRINGS_NOT_SUPPORTED: case BINARY_BLOB_FORMAT_ERROR: case TOO_BIG: case BUCKET_ERROR: // maybe. No point retrying. case INTERNAL_ERROR: // maybe. No point retrying. return true; case REJECTED_OVERLOAD: case TOO_MANY_RETRIES_IN_BLOCKS: case ROUTE_NOT_FOUND: case ROUTE_REALLY_NOT_FOUND: return false; default: Logger.error(InsertException.class, "Error unknown to isFatal(): "+getMessage(mode)); return false; } } /** * Construct an InsertException from a bunch of error codes, typically from a splitfile insert. */ public static InsertException construct(FailureCodeTracker errors) { if(errors == null) return null; if(errors.isEmpty()) return null; if(errors.isOneCodeOnly()) { return new InsertException(errors.getFirstCodeInsert()); } InsertExceptionMode mode; if(errors.isFatal(true)) mode = InsertExceptionMode.FATAL_ERRORS_IN_BLOCKS; else mode = InsertExceptionMode.TOO_MANY_RETRIES_IN_BLOCKS; return new InsertException(mode, errors, null); } @Override public InsertException clone() { // Cloneable shuts up findbugs, but we need to deep copy errorCodes. return new InsertException(this); } }