ErrorType.java
/*
* Copyright (C) 2016 Hobrasoft s.r.o.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cz.hobrasoft.pdfmu.error;
import cz.hobrasoft.pdfmu.IntProperties;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.logging.Logger;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.functors.StringValueTransformer;
/**
* Contains all the types of errors (failing conditions) that can happen in
* PDFMU. Every error type has a code and a message template associated. The
* codes are loaded from the resource {@value #CODES_RESOURCE_NAME} and the
* messages are loaded from the resource bundle
* {@value #MESSAGES_RESOURCE_BUNDLE_BASE_NAME}. The keys in both of the
* resource files must match the strings returned by
* {@link ErrorType#toString()} (which returns the enum constant name by
* default).
*
* <p>
* Assertions are in place that ensure that every error type has a code and a
* message associated, and that the codes are unique. If assertions are disabled
* and a resource is missing or incompatible, default values are provided.
*
* <p>
* How to add a new error type:
* <ul>
* <li>Add an enum constant in {@link ErrorType}
* <li>Add a corresponding code in <tt>ErrorCodes.properties</tt>
* <li>Add a corresponding message in <tt>ErrorMessages.properties</tt>
* </ul>
*
* @author <a href="mailto:filip.bartek@hobrasoft.cz">Filip Bartek</a>
*/
public enum ErrorType {
PARSER_UNKNOWN,
PARSER_UNRECOGNIZED_COMMAND,
PARSER_UNRECOGNIZED_ARGUMENT,
PARSER_INVALID_CHOICE,
PARSER_TOO_FEW_ARGUMENTS,
PARSER_EXPECTED_ONE_ARGUMENT,
INPUT_NOT_VALID_PDF,
INPUT_NOT_FOUND,
INPUT_CLOSE,
OUTPUT_NOT_SPECIFIED,
OUTPUT_EXISTS_FORCE_NOT_SET,
OUTPUT_OPEN,
OUTPUT_STAMPER_OPEN,
OUTPUT_STAMPER_CLOSE,
OUTPUT_CLOSE,
OUTPUT_WRITE,
SIGNATURE_ADD_KEYSTORE_TYPE_UNSUPPORTED,
SIGNATURE_ADD_KEYSTORE_FILE_NOT_SPECIFIED,
SIGNATURE_ADD_KEYSTORE_FILE_OPEN,
SIGNATURE_ADD_KEYSTORE_LOAD,
SIGNATURE_ADD_KEYSTORE_FILE_CLOSE,
SIGNATURE_ADD_KEYSTORE_ALIASES,
SIGNATURE_ADD_KEYSTORE_EMPTY,
SIGNATURE_ADD_KEYSTORE_ALIAS_MISSING,
SIGNATURE_ADD_KEYSTORE_ALIAS_EXCEPTION,
SIGNATURE_ADD_KEYSTORE_ALIAS_NOT_KEY,
SIGNATURE_ADD_KEYSTORE_ALIAS_KEY_EXCEPTION,
SIGNATURE_ADD_KEYSTORE_PRIVATE_KEY,
SIGNATURE_ADD_KEYSTORE_CERTIFICATE_CHAIN,
SIGNATURE_ADD_UNSUPPORTED_DIGEST_ALGORITHM,
SIGNATURE_ADD_FAIL,
SIGNATURE_ADD_TSA_UNTRUSTED,
SIGNATURE_ADD_TSA_UNAUTHORIZED,
SIGNATURE_ADD_TSA_LOGIN_FAIL,
SIGNATURE_ADD_TSA_UNREACHABLE,
SIGNATURE_ADD_TSA_BAD_CERTIFICATE,
SIGNATURE_ADD_TSA_HANDSHAKE_FAILURE,
SIGNATURE_ADD_TSA_SSL_HANDSHAKE_EXCEPTION,
SIGNATURE_ADD_TSA_SSL_FATAL_ALERT,
SIGNATURE_ADD_TSA_INVALID_URL,
SIGNATURE_ADD_SIGNATURE_EXCEPTION,
ATTACH_FAIL,
ATTACH_ATTACHMENT_EQUALS_OUTPUT,
SSL_TRUSTSTORE_NOT_FOUND,
SSL_TRUSTSTORE_INCORRECT_TYPE,
SSL_TRUSTSTORE_EMPTY,
SSL_KEYSTORE_NOT_FOUND;
/**
* The default error code. It is used for error types that have no code
* associated.
*
* <p>
* Value: {@value #DEFAULT_ERROR_CODE}
*/
public static final int DEFAULT_ERROR_CODE = -1;
/**
* The name of the resource that contains the error codes. The resource must
* be a properties file. It is located using
* {@link ClassLoader#getResourceAsStream(String)} and loaded using
* {@link IntProperties#load(InputStream)}.
*
* <p>
* Value: {@value #CODES_RESOURCE_NAME}
*/
public static final String CODES_RESOURCE_NAME = "cz/hobrasoft/pdfmu/error/ErrorCodes.properties";
/**
* The base name of the resource bundle that contains the error messages.
* The resource bundle is loaded using
* {@link ResourceBundle#getBundle(String)}.
*
* <p>
* Value: {@value #MESSAGES_RESOURCE_BUNDLE_BASE_NAME}
*/
public static final String MESSAGES_RESOURCE_BUNDLE_BASE_NAME = "cz.hobrasoft.pdfmu.error.ErrorMessages";
private static final Logger LOGGER = Logger.getLogger(ErrorType.class.getName());
private static final IntProperties CODES = new IntProperties(DEFAULT_ERROR_CODE);
private static ResourceBundle messages = null;
/**
* @return true iff each of the constants of this enum is a key in both
* {@link #CODES} and {@link #messages}.
*/
private static boolean codesAndMessagesAvailable() {
ErrorType[] enumKeyArray = ErrorType.values();
List<ErrorType> enumKeyList = Arrays.asList(enumKeyArray);
Collection<String> enumKeyStrings = CollectionUtils.collect(enumKeyList, StringValueTransformer.stringValueTransformer());
Set<String> codeKeySet = CODES.stringPropertyNames();
assert messages != null;
Set<String> messageKeySet = messages.keySet();
return codeKeySet.containsAll(enumKeyStrings) && messageKeySet.containsAll(enumKeyStrings);
}
/**
* @return true iff the codes stored in {@link #CODES} are pairwise
* different.
*/
private static boolean codesUnique() {
Collection<Integer> codes = CODES.intPropertyValues();
Set<Integer> codesUnique = new HashSet<>(codes);
assert codes.size() >= codesUnique.size();
return codes.size() == codesUnique.size();
}
/**
* Loads error codes from the properties resource
* {@link #CODES_RESOURCE_NAME} and stores them in {@link #CODES}.
*/
private static void loadErrorCodes() {
ClassLoader classLoader = ErrorType.class.getClassLoader();
InputStream in = classLoader.getResourceAsStream(CODES_RESOURCE_NAME);
if (in != null) {
try {
CODES.load(in);
} catch (IOException ex) {
LOGGER.severe(String.format("Could not load the error codes properties file: %s", ex));
}
try {
in.close();
} catch (IOException ex) {
LOGGER.severe(String.format("Could not close the error codes properties file: %s", ex));
}
} else {
LOGGER.severe("Could not open the error codes properties file.");
}
}
/**
* Loads error messages from the resource bundle
* {@link #MESSAGES_RESOURCE_BUNDLE_BASE_NAME} and stores them in
* {@link #messages}.
*/
private static void loadErrorMessages() {
try {
messages = ResourceBundle.getBundle(MESSAGES_RESOURCE_BUNDLE_BASE_NAME);
} catch (MissingResourceException ex) {
LOGGER.severe(String.format("Could not load the error messages resource bundle: %s", ex));
}
}
static {
// Load error codes and messages before OperationException is instantiated
loadErrorCodes();
loadErrorMessages();
assert messages != null;
// Assert that a code and a message is available for every enum constant
assert codesAndMessagesAvailable();
// Assert that the codes are pairwise different
assert codesUnique();
}
/**
* Returns the error code associated with this error type. The code should
* uniquely identify the error. The value of {@link #DEFAULT_ERROR_CODE} is
* returned if no code is associated with this error type. The error codes
* are loaded from the resource {@value #CODES_RESOURCE_NAME} when the first
* {@link ErrorType} is instantiated.
*
* @return the error code associated with this error type, or
* {@value #DEFAULT_ERROR_CODE} if none is associated
*/
public int getCode() {
return CODES.getIntProperty(toString());
}
/**
* Returns the message pattern associated with this error type. The message
* patterns are loaded from the resource bundle
* {@value #MESSAGES_RESOURCE_BUNDLE_BASE_NAME} when the first
* {@link ErrorType} is instantiated.
*
* @return the message pattern associated with this error type, or null if
* none is associated
*/
public String getMessagePattern() {
String key = toString();
assert messages != null;
if (messages.containsKey(key)) {
return messages.getString(key);
} else {
return null;
}
}
}