View Javadoc
1   /* 
2    * Copyright (C) 2016 Hobrasoft s.r.o.
3    *
4    * This program is free software: you can redistribute it and/or modify
5    * it under the terms of the GNU Affero General Public License as published by
6    * the Free Software Foundation, either version 3 of the License, or
7    * (at your option) any later version.
8    *
9    * This program is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   * GNU Affero General Public License for more details.
13   *
14   * You should have received a copy of the GNU Affero General Public License
15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16   */
17  package cz.hobrasoft.pdfmu.error;
18  
19  import cz.hobrasoft.pdfmu.IntProperties;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.util.Arrays;
23  import java.util.Collection;
24  import java.util.HashSet;
25  import java.util.List;
26  import java.util.MissingResourceException;
27  import java.util.ResourceBundle;
28  import java.util.Set;
29  import java.util.logging.Logger;
30  import org.apache.commons.collections4.CollectionUtils;
31  import org.apache.commons.collections4.functors.StringValueTransformer;
32  
33  /**
34   * Contains all the types of errors (failing conditions) that can happen in
35   * PDFMU. Every error type has a code and a message template associated. The
36   * codes are loaded from the resource {@value #CODES_RESOURCE_NAME} and the
37   * messages are loaded from the resource bundle
38   * {@value #MESSAGES_RESOURCE_BUNDLE_BASE_NAME}. The keys in both of the
39   * resource files must match the strings returned by
40   * {@link ErrorType#toString()} (which returns the enum constant name by
41   * default).
42   *
43   * <p>
44   * Assertions are in place that ensure that every error type has a code and a
45   * message associated, and that the codes are unique. If assertions are disabled
46   * and a resource is missing or incompatible, default values are provided.
47   *
48   * <p>
49   * How to add a new error type:
50   * <ul>
51   * <li>Add an enum constant in {@link ErrorType}
52   * <li>Add a corresponding code in <tt>ErrorCodes.properties</tt>
53   * <li>Add a corresponding message in <tt>ErrorMessages.properties</tt>
54   * </ul>
55   *
56   * @author <a href="mailto:filip.bartek@hobrasoft.cz">Filip Bartek</a>
57   */
58  public enum ErrorType {
59      PARSER_UNKNOWN,
60      PARSER_UNRECOGNIZED_COMMAND,
61      PARSER_UNRECOGNIZED_ARGUMENT,
62      PARSER_INVALID_CHOICE,
63      PARSER_TOO_FEW_ARGUMENTS,
64      PARSER_EXPECTED_ONE_ARGUMENT,
65      INPUT_NOT_VALID_PDF,
66      INPUT_NOT_FOUND,
67      INPUT_CLOSE,
68      OUTPUT_NOT_SPECIFIED,
69      OUTPUT_EXISTS_FORCE_NOT_SET,
70      OUTPUT_OPEN,
71      OUTPUT_STAMPER_OPEN,
72      OUTPUT_STAMPER_CLOSE,
73      OUTPUT_CLOSE,
74      OUTPUT_WRITE,
75      SIGNATURE_ADD_KEYSTORE_TYPE_UNSUPPORTED,
76      SIGNATURE_ADD_KEYSTORE_FILE_NOT_SPECIFIED,
77      SIGNATURE_ADD_KEYSTORE_FILE_OPEN,
78      SIGNATURE_ADD_KEYSTORE_LOAD,
79      SIGNATURE_ADD_KEYSTORE_FILE_CLOSE,
80      SIGNATURE_ADD_KEYSTORE_ALIASES,
81      SIGNATURE_ADD_KEYSTORE_EMPTY,
82      SIGNATURE_ADD_KEYSTORE_ALIAS_MISSING,
83      SIGNATURE_ADD_KEYSTORE_ALIAS_EXCEPTION,
84      SIGNATURE_ADD_KEYSTORE_ALIAS_NOT_KEY,
85      SIGNATURE_ADD_KEYSTORE_ALIAS_KEY_EXCEPTION,
86      SIGNATURE_ADD_KEYSTORE_PRIVATE_KEY,
87      SIGNATURE_ADD_KEYSTORE_CERTIFICATE_CHAIN,
88      SIGNATURE_ADD_UNSUPPORTED_DIGEST_ALGORITHM,
89      SIGNATURE_ADD_FAIL,
90      SIGNATURE_ADD_TSA_UNTRUSTED,
91      SIGNATURE_ADD_TSA_UNAUTHORIZED,
92      SIGNATURE_ADD_TSA_LOGIN_FAIL,
93      SIGNATURE_ADD_TSA_UNREACHABLE,
94      SIGNATURE_ADD_TSA_BAD_CERTIFICATE,
95      SIGNATURE_ADD_TSA_HANDSHAKE_FAILURE,
96      SIGNATURE_ADD_TSA_SSL_HANDSHAKE_EXCEPTION,
97      SIGNATURE_ADD_TSA_SSL_FATAL_ALERT,
98      SIGNATURE_ADD_TSA_INVALID_URL,
99      SIGNATURE_ADD_SIGNATURE_EXCEPTION,
100     ATTACH_FAIL,
101     ATTACH_ATTACHMENT_EQUALS_OUTPUT,
102     SSL_TRUSTSTORE_NOT_FOUND,
103     SSL_TRUSTSTORE_INCORRECT_TYPE,
104     SSL_TRUSTSTORE_EMPTY,
105     SSL_KEYSTORE_NOT_FOUND;
106 
107     /**
108      * The default error code. It is used for error types that have no code
109      * associated.
110      *
111      * <p>
112      * Value: {@value #DEFAULT_ERROR_CODE}
113      */
114     public static final int DEFAULT_ERROR_CODE = -1;
115 
116     /**
117      * The name of the resource that contains the error codes. The resource must
118      * be a properties file. It is located using
119      * {@link ClassLoader#getResourceAsStream(String)} and loaded using
120      * {@link IntProperties#load(InputStream)}.
121      *
122      * <p>
123      * Value: {@value #CODES_RESOURCE_NAME}
124      */
125     public static final String CODES_RESOURCE_NAME = "cz/hobrasoft/pdfmu/error/ErrorCodes.properties";
126 
127     /**
128      * The base name of the resource bundle that contains the error messages.
129      * The resource bundle is loaded using
130      * {@link ResourceBundle#getBundle(String)}.
131      *
132      * <p>
133      * Value: {@value #MESSAGES_RESOURCE_BUNDLE_BASE_NAME}
134      */
135     public static final String MESSAGES_RESOURCE_BUNDLE_BASE_NAME = "cz.hobrasoft.pdfmu.error.ErrorMessages";
136 
137     private static final Logger LOGGER = Logger.getLogger(ErrorType.class.getName());
138 
139     private static final IntProperties CODES = new IntProperties(DEFAULT_ERROR_CODE);
140     private static ResourceBundle messages = null;
141 
142     /**
143      * @return true iff each of the constants of this enum is a key in both
144      * {@link #CODES} and {@link #messages}.
145      */
146     private static boolean codesAndMessagesAvailable() {
147         ErrorType[] enumKeyArray = ErrorType.values();
148         List<ErrorType> enumKeyList = Arrays.asList(enumKeyArray);
149         Collection<String> enumKeyStrings = CollectionUtils.collect(enumKeyList, StringValueTransformer.stringValueTransformer());
150 
151         Set<String> codeKeySet = CODES.stringPropertyNames();
152         assert messages != null;
153         Set<String> messageKeySet = messages.keySet();
154 
155         return codeKeySet.containsAll(enumKeyStrings) && messageKeySet.containsAll(enumKeyStrings);
156     }
157 
158     /**
159      * @return true iff the codes stored in {@link #CODES} are pairwise
160      * different.
161      */
162     private static boolean codesUnique() {
163         Collection<Integer> codes = CODES.intPropertyValues();
164         Set<Integer> codesUnique = new HashSet<>(codes);
165         assert codes.size() >= codesUnique.size();
166         return codes.size() == codesUnique.size();
167     }
168 
169     /**
170      * Loads error codes from the properties resource
171      * {@link #CODES_RESOURCE_NAME} and stores them in {@link #CODES}.
172      */
173     private static void loadErrorCodes() {
174         ClassLoader classLoader = ErrorType.class.getClassLoader();
175         InputStream in = classLoader.getResourceAsStream(CODES_RESOURCE_NAME);
176         if (in != null) {
177             try {
178                 CODES.load(in);
179             } catch (IOException ex) {
180                 LOGGER.severe(String.format("Could not load the error codes properties file: %s", ex));
181             }
182             try {
183                 in.close();
184             } catch (IOException ex) {
185                 LOGGER.severe(String.format("Could not close the error codes properties file: %s", ex));
186             }
187         } else {
188             LOGGER.severe("Could not open the error codes properties file.");
189         }
190     }
191 
192     /**
193      * Loads error messages from the resource bundle
194      * {@link #MESSAGES_RESOURCE_BUNDLE_BASE_NAME} and stores them in
195      * {@link #messages}.
196      */
197     private static void loadErrorMessages() {
198         try {
199             messages = ResourceBundle.getBundle(MESSAGES_RESOURCE_BUNDLE_BASE_NAME);
200         } catch (MissingResourceException ex) {
201             LOGGER.severe(String.format("Could not load the error messages resource bundle: %s", ex));
202         }
203     }
204 
205     static {
206         // Load error codes and messages before OperationException is instantiated
207         loadErrorCodes();
208         loadErrorMessages();
209         assert messages != null;
210 
211         // Assert that a code and a message is available for every enum constant
212         assert codesAndMessagesAvailable();
213 
214         // Assert that the codes are pairwise different
215         assert codesUnique();
216     }
217 
218     /**
219      * Returns the error code associated with this error type. The code should
220      * uniquely identify the error. The value of {@link #DEFAULT_ERROR_CODE} is
221      * returned if no code is associated with this error type. The error codes
222      * are loaded from the resource {@value #CODES_RESOURCE_NAME} when the first
223      * {@link ErrorType} is instantiated.
224      *
225      * @return the error code associated with this error type, or
226      * {@value #DEFAULT_ERROR_CODE} if none is associated
227      */
228     public int getCode() {
229         return CODES.getIntProperty(toString());
230     }
231 
232     /**
233      * Returns the message pattern associated with this error type. The message
234      * patterns are loaded from the resource bundle
235      * {@value #MESSAGES_RESOURCE_BUNDLE_BASE_NAME} when the first
236      * {@link ErrorType} is instantiated.
237      *
238      * @return the message pattern associated with this error type, or null if
239      * none is associated
240      */
241     public String getMessagePattern() {
242         String key = toString();
243         assert messages != null;
244         if (messages.containsKey(key)) {
245             return messages.getString(key);
246         } else {
247             return null;
248         }
249     }
250 }