View Javadoc
1   /*
2    * Prometheus: Application Framework
3    * Copyright 2012-2026. Tony Washer
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6    * use this file except in compliance with the License.  You may obtain a copy
7    * of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
14   * License for the specific language governing permissions and limitations under
15   * the License.
16   */
17  package io.github.tonywasher.joceanus.prometheus.data;
18  
19  import io.github.tonywasher.joceanus.gordianknot.api.base.GordianException;
20  import io.github.tonywasher.joceanus.gordianknot.api.keyset.GordianKeySet;
21  import io.github.tonywasher.joceanus.metis.data.MetisDataDifference;
22  import io.github.tonywasher.joceanus.metis.data.MetisDataType;
23  import io.github.tonywasher.joceanus.metis.field.MetisFieldItem.MetisFieldDef;
24  import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
25  import io.github.tonywasher.joceanus.oceanus.convert.OceanusDataConverter;
26  import io.github.tonywasher.joceanus.oceanus.date.OceanusDate;
27  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusDecimal;
28  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusMoney;
29  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusPrice;
30  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusRate;
31  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusRatio;
32  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusUnits;
33  import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
34  import io.github.tonywasher.joceanus.prometheus.exc.PrometheusDataException;
35  import io.github.tonywasher.joceanus.prometheus.exc.PrometheusLogicException;
36  import io.github.tonywasher.joceanus.prometheus.exc.PrometheusSecurityException;
37  
38  import java.util.EnumMap;
39  import java.util.Map;
40  
41  /**
42   * Encryptor/Decryptor.
43   */
44  public class PrometheusEncryptor {
45      /**
46       * Encrypted data conversion failure message.
47       */
48      private static final String ERROR_BYTES_CONVERT = "Failed to convert value from bytes";
49  
50      /**
51       * Invalid class error text.
52       */
53      private static final String ERROR_CLASS = "Invalid Object Class for Encryption ";
54  
55      /**
56       * Unsupported dataType error text.
57       */
58      private static final String ERROR_DATATYPE = "Unsupported Data Type";
59  
60      /**
61       * The Encryptor map.
62       */
63      private static final Map<MetisDataType, PrometheusDataEncryptor> ENCRYPTORS = buildEncryptorMap();
64  
65      /**
66       * The KeySet.
67       */
68      private final GordianKeySet theKeySet;
69  
70      /**
71       * The Data formatter.
72       */
73      private final OceanusDataFormatter theFormatter;
74  
75      /**
76       * Constructor.
77       *
78       * @param pFormatter the formatter
79       * @param pKeySet    the keySet
80       */
81      public PrometheusEncryptor(final OceanusDataFormatter pFormatter,
82                                 final GordianKeySet pKeySet) {
83          theFormatter = pFormatter;
84          theKeySet = pKeySet;
85      }
86  
87      /**
88       * Obtain the keySet.
89       *
90       * @return the keySet
91       */
92      public GordianKeySet getKeySet() {
93          return theKeySet;
94      }
95  
96      /**
97       * Encrypt a value.
98       *
99       * @param pValue the value to encrypt.
100      * @return the encryptedBytes
101      * @throws OceanusException on error
102      */
103     public byte[] encryptValue(final Object pValue) throws OceanusException {
104         /* Protect against exceptions */
105         try {
106             /* Access the encryptor */
107             final MetisDataType myDataType = getDataTypeForValue(pValue);
108             final PrometheusDataEncryptor myEncryptor = ENCRYPTORS.get(myDataType);
109 
110             final byte[] myBytes = myEncryptor.convertValue(theFormatter, pValue);
111             return theKeySet == null ? null : theKeySet.encryptBytes(myBytes);
112         } catch (GordianException e) {
113             throw new PrometheusSecurityException(e);
114         }
115     }
116 
117     /**
118      * Encrypt a value.
119      *
120      * @param pCurrent the current value
121      * @param pValue   the value to encrypt.
122      * @return the encryptedPair.
123      * @throws OceanusException on error
124      */
125     public PrometheusEncryptedPair encryptValue(final PrometheusEncryptedPair pCurrent,
126                                                 final Object pValue) throws OceanusException {
127         /* If we are passed a null value just return null */
128         if (pValue == null) {
129             return null;
130         }
131 
132         /* If we have no keySet or else a different keySet, ignore the current value */
133         PrometheusEncryptedPair myCurrent = pCurrent;
134         if (myCurrent != null
135                 && (theKeySet == null || !theKeySet.equals(myCurrent.getKeySet()))) {
136             myCurrent = null;
137         }
138 
139         /* If the value is not changed return the current value */
140         if (myCurrent != null
141                 && MetisDataDifference.isEqual(myCurrent.getValue(), pValue)) {
142             return pCurrent;
143         }
144 
145         /* Encrypt the data */
146         final byte[] myEncrypted = encryptValue(pValue);
147         return new PrometheusEncryptedPair(theKeySet, pValue, myEncrypted);
148     }
149 
150     /**
151      * Encrypt a value.
152      *
153      * @param pValue the value to encrypt.
154      * @param pField the field definition
155      * @return the encryptedPair.
156      * @throws OceanusException on error
157      */
158     public PrometheusEncryptedPair encryptValue(final Object pValue,
159                                                 final MetisFieldDef pField) throws OceanusException {
160         /* Protect against exceptions */
161         try {
162             /* If we are passed a null value just return null */
163             if (pValue == null) {
164                 return null;
165             }
166 
167             /* Handle Context dataType */
168             MetisDataType myDataType = pField.getDataType();
169             if (myDataType == MetisDataType.CONTEXT) {
170                 myDataType = getDataTypeForValue(pValue);
171             }
172 
173             /* Access the encryptor */
174             final PrometheusDataEncryptor myEncryptor = ENCRYPTORS.get(myDataType);
175             if (myEncryptor == null) {
176                 throw new PrometheusLogicException(ERROR_DATATYPE);
177             }
178 
179             /* Encrypt the data */
180             final byte[] myBytes = myEncryptor.convertValue(theFormatter, pValue);
181             final byte[] myEncrypted = theKeySet.encryptBytes(myBytes);
182             return new PrometheusEncryptedPair(theKeySet, pValue, myEncrypted);
183         } catch (GordianException e) {
184             throw new PrometheusSecurityException(e);
185         }
186     }
187 
188     /**
189      * Decrypt bytes.
190      *
191      * @param pBytes the bytes to decrypt.
192      * @param pField the field definition
193      * @return the encryptedPair.
194      * @throws OceanusException on error
195      */
196     PrometheusEncryptedPair decryptValue(final byte[] pBytes,
197                                          final MetisFieldDef pField) throws OceanusException {
198         /* Protect agains exceptions */
199         try {
200             /* Access the encryptor */
201             final PrometheusDataEncryptor myEncryptor = ENCRYPTORS.get(pField.getDataType());
202             if (myEncryptor == null) {
203                 throw new PrometheusLogicException(ERROR_DATATYPE);
204             }
205 
206             /* Decrypt the data */
207             final byte[] myDecrypted = theKeySet.decryptBytes(pBytes);
208             final Object myValue = myEncryptor.parseValue(theFormatter, myDecrypted);
209             return new PrometheusEncryptedPair(theKeySet, myValue, pBytes);
210         } catch (GordianException e) {
211             throw new PrometheusSecurityException(e);
212         }
213     }
214 
215     /**
216      * Decrypt bytes.
217      *
218      * @param pBytes the bytes to decrypt.
219      * @param pClazz the class to decrypt to
220      * @return the encryptedPair.
221      * @throws OceanusException on error
222      */
223     PrometheusEncryptedPair decryptValue(final byte[] pBytes,
224                                          final Class<?> pClazz) throws OceanusException {
225         /* Protect agains exceptions */
226         try {
227             /* Access the encryptor */
228             final MetisDataType myDataType = getDataTypeForClass(pClazz);
229             final PrometheusDataEncryptor myEncryptor = ENCRYPTORS.get(myDataType);
230             if (myEncryptor == null) {
231                 throw new PrometheusLogicException(ERROR_DATATYPE);
232             }
233 
234             /* Decrypt the data */
235             final byte[] myDecrypted = theKeySet.decryptBytes(pBytes);
236             final Object myValue = myEncryptor.parseValue(theFormatter, myDecrypted);
237             return new PrometheusEncryptedPair(theKeySet, myValue, pBytes);
238         } catch (GordianException e) {
239             throw new PrometheusSecurityException(e);
240         }
241     }
242 
243     /**
244      * Determine dataType.
245      *
246      * @param pValue the value
247      * @return the dataType
248      * @throws OceanusException on error
249      */
250     public static MetisDataType getDataTypeForValue(final Object pValue) throws OceanusException {
251         if (pValue instanceof String) {
252             return MetisDataType.STRING;
253         }
254         if (pValue instanceof Short) {
255             return MetisDataType.SHORT;
256         }
257         if (pValue instanceof Integer) {
258             return MetisDataType.INTEGER;
259         }
260         if (pValue instanceof Long) {
261             return MetisDataType.LONG;
262         }
263         if (pValue instanceof Boolean) {
264             return MetisDataType.BOOLEAN;
265         }
266         if (pValue instanceof char[]) {
267             return MetisDataType.CHARARRAY;
268         }
269 
270         /* Handle decimal instances */
271         if (pValue instanceof OceanusDate) {
272             return MetisDataType.DATE;
273         }
274         if (pValue instanceof OceanusUnits) {
275             return MetisDataType.UNITS;
276         }
277         if (pValue instanceof OceanusRate) {
278             return MetisDataType.RATE;
279         }
280         if (pValue instanceof OceanusPrice) {
281             return MetisDataType.PRICE;
282         }
283         if (pValue instanceof OceanusMoney) {
284             return MetisDataType.MONEY;
285         }
286         if (pValue instanceof OceanusRatio) {
287             return MetisDataType.RATIO;
288         }
289 
290         /* Unsupported so reject */
291         throw new PrometheusLogicException(ERROR_CLASS
292                 + pValue.getClass().getCanonicalName());
293     }
294 
295     /**
296      * Determine dataType.
297      *
298      * @param pClazz the class
299      * @return the dataType
300      * @throws OceanusException on error
301      */
302     static MetisDataType getDataTypeForClass(final Class<?> pClazz) throws OceanusException {
303         if (String.class.equals(pClazz)) {
304             return MetisDataType.STRING;
305         }
306         if (Short.class.equals(pClazz)) {
307             return MetisDataType.SHORT;
308         }
309         if (Integer.class.equals(pClazz)) {
310             return MetisDataType.INTEGER;
311         }
312         if (Long.class.equals(pClazz)) {
313             return MetisDataType.LONG;
314         }
315         if (Boolean.class.equals(pClazz)) {
316             return MetisDataType.BOOLEAN;
317         }
318         if (char[].class.equals(pClazz)) {
319             return MetisDataType.CHARARRAY;
320         }
321 
322         /* Handle decimal instances */
323         if (OceanusDate.class.equals(pClazz)) {
324             return MetisDataType.DATE;
325         }
326         if (OceanusUnits.class.equals(pClazz)) {
327             return MetisDataType.UNITS;
328         }
329         if (OceanusRate.class.equals(pClazz)) {
330             return MetisDataType.RATE;
331         }
332         if (OceanusPrice.class.equals(pClazz)) {
333             return MetisDataType.PRICE;
334         }
335         if (OceanusMoney.class.equals(pClazz)) {
336             return MetisDataType.MONEY;
337         }
338         if (OceanusRatio.class.equals(pClazz)) {
339             return MetisDataType.RATIO;
340         }
341 
342         /* Unsupported so reject */
343         throw new PrometheusLogicException(ERROR_CLASS
344                 + pClazz.getCanonicalName());
345     }
346 
347     /**
348      * Build the encryptor map.
349      *
350      * @return the map
351      */
352     private static Map<MetisDataType, PrometheusDataEncryptor> buildEncryptorMap() {
353         final Map<MetisDataType, PrometheusDataEncryptor> myMap = new EnumMap<>(MetisDataType.class);
354         myMap.put(MetisDataType.DATE, new PrometheusDateEncryptor());
355         myMap.put(MetisDataType.SHORT, new PrometheusShortEncryptor());
356         myMap.put(MetisDataType.INTEGER, new PrometheusIntegerEncryptor());
357         myMap.put(MetisDataType.LONG, new PrometheusLongEncryptor());
358         myMap.put(MetisDataType.STRING, new PrometheusStringEncryptor());
359         myMap.put(MetisDataType.CHARARRAY, new PrometheusCharArrayEncryptor());
360         myMap.put(MetisDataType.BOOLEAN, new PrometheusBooleanEncryptor());
361         myMap.put(MetisDataType.MONEY, new PrometheusMoneyEncryptor());
362         myMap.put(MetisDataType.PRICE, new PrometheusPriceEncryptor());
363         myMap.put(MetisDataType.RATE, new PrometheusRateEncryptor());
364         myMap.put(MetisDataType.UNITS, new PrometheusUnitsEncryptor());
365         myMap.put(MetisDataType.RATIO, new PrometheusRatioEncryptor());
366         return myMap;
367     }
368 
369     /**
370      * Adopt Encryption.
371      *
372      * @param pTarget the target field
373      * @param pSource the source field
374      * @throws OceanusException on error
375      */
376     public void adoptEncryption(final PrometheusEncryptedPair pTarget,
377                                 final PrometheusEncryptedPair pSource) throws OceanusException {
378         /* Adopt the encryption */
379         pTarget.adoptEncryption(this, pSource);
380     }
381 
382     /**
383      * Encryptor Base.
384      */
385     private interface PrometheusDataEncryptor {
386         /**
387          * Convert a value to bytes.
388          *
389          * @param pFormatter the data formatter
390          * @param pValue     the value to convert.
391          * @return the converted bytes.
392          * @throws OceanusException on error
393          */
394         byte[] convertValue(OceanusDataFormatter pFormatter,
395                             Object pValue) throws OceanusException;
396 
397         /**
398          * Parse a value from bytes.
399          *
400          * @param pFormatter the data formatter
401          * @param pBytes     the bytes to parse.
402          * @return the parsed value.
403          * @throws OceanusException on error
404          */
405         Object parseValue(OceanusDataFormatter pFormatter,
406                           byte[] pBytes) throws OceanusException;
407     }
408 
409     /**
410      * DateEncryptor.
411      */
412     private static final class PrometheusDateEncryptor
413             implements PrometheusDataEncryptor {
414         @Override
415         public byte[] convertValue(final OceanusDataFormatter pFormatter,
416                                    final Object pValue) {
417             return pFormatter.getDateFormatter().toBytes((OceanusDate) pValue);
418         }
419 
420         @Override
421         public Object parseValue(final OceanusDataFormatter pFormatter,
422                                  final byte[] pBytes) throws OceanusException {
423             try {
424                 return pFormatter.getDateFormatter().fromBytes(pBytes);
425             } catch (IllegalArgumentException e) {
426                 throw new PrometheusDataException(ERROR_BYTES_CONVERT, e);
427             }
428         }
429     }
430 
431     /**
432      * IntegerEncryptor.
433      */
434     private static final class PrometheusShortEncryptor
435             implements PrometheusDataEncryptor {
436         @Override
437         public byte[] convertValue(final OceanusDataFormatter pFormatter,
438                                    final Object pValue) {
439             return OceanusDataConverter.shortToByteArray((short) pValue);
440         }
441 
442         @Override
443         public Object parseValue(final OceanusDataFormatter pFormatter,
444                                  final byte[] pBytes) throws OceanusException {
445             return OceanusDataConverter.byteArrayToShort(pBytes);
446         }
447     }
448 
449     /**
450      * IntegerEncryptor.
451      */
452     private static final class PrometheusIntegerEncryptor
453             implements PrometheusDataEncryptor {
454         @Override
455         public byte[] convertValue(final OceanusDataFormatter pFormatter,
456                                    final Object pValue) {
457             return OceanusDataConverter.integerToByteArray((int) pValue);
458         }
459 
460         @Override
461         public Object parseValue(final OceanusDataFormatter pFormatter,
462                                  final byte[] pBytes) throws OceanusException {
463             return OceanusDataConverter.byteArrayToInteger(pBytes);
464         }
465     }
466 
467     /**
468      * LongEncryptor.
469      */
470     private static final class PrometheusLongEncryptor
471             implements PrometheusDataEncryptor {
472         @Override
473         public byte[] convertValue(final OceanusDataFormatter pFormatter,
474                                    final Object pValue) {
475             return OceanusDataConverter.longToByteArray((long) pValue);
476         }
477 
478         @Override
479         public Object parseValue(final OceanusDataFormatter pFormatter,
480                                  final byte[] pBytes) throws OceanusException {
481             return OceanusDataConverter.byteArrayToLong(pBytes);
482         }
483     }
484 
485     /**
486      * BooleanEncryptor.
487      */
488     private static final class PrometheusBooleanEncryptor
489             implements PrometheusDataEncryptor {
490         @Override
491         public byte[] convertValue(final OceanusDataFormatter pFormatter,
492                                    final Object pValue) {
493             return OceanusDataConverter.stringToByteArray(pValue.toString());
494         }
495 
496         @Override
497         public Object parseValue(final OceanusDataFormatter pFormatter,
498                                  final byte[] pBytes) {
499             final String myBoolString = OceanusDataConverter.byteArrayToString(pBytes);
500             return Boolean.parseBoolean(myBoolString);
501         }
502     }
503 
504     /**
505      * StringEncryptor.
506      */
507     private static final class PrometheusStringEncryptor
508             implements PrometheusDataEncryptor {
509         @Override
510         public byte[] convertValue(final OceanusDataFormatter pFormatter,
511                                    final Object pValue) {
512             return OceanusDataConverter.stringToByteArray((String) pValue);
513         }
514 
515         @Override
516         public Object parseValue(final OceanusDataFormatter pFormatter,
517                                  final byte[] pBytes) {
518             return OceanusDataConverter.byteArrayToString(pBytes);
519         }
520     }
521 
522     /**
523      * CharArrayEncryptor.
524      */
525     private static final class PrometheusCharArrayEncryptor
526             implements PrometheusDataEncryptor {
527         @Override
528         public byte[] convertValue(final OceanusDataFormatter pFormatter,
529                                    final Object pValue) throws OceanusException {
530             return OceanusDataConverter.charsToByteArray((char[]) pValue);
531         }
532 
533         @Override
534         public Object parseValue(final OceanusDataFormatter pFormatter,
535                                  final byte[] pBytes) throws OceanusException {
536             return OceanusDataConverter.bytesToCharArray(pBytes);
537         }
538     }
539 
540     /**
541      * DecimalEncryptor.
542      */
543     private abstract static class PrometheusDecimalEncryptor
544             implements PrometheusDataEncryptor {
545         @Override
546         public byte[] convertValue(final OceanusDataFormatter pFormatter,
547                                    final Object pValue) {
548             return ((OceanusDecimal) pValue).toBytes();
549         }
550     }
551 
552     /**
553      * MoneyEncryptor.
554      */
555     private static final class PrometheusMoneyEncryptor
556             extends PrometheusDecimalEncryptor {
557         @Override
558         public Object parseValue(final OceanusDataFormatter pFormatter,
559                                  final byte[] pBytes) throws OceanusException {
560             return new OceanusMoney(pBytes);
561         }
562     }
563 
564     /**
565      * PriceEncryptor.
566      */
567     private static final class PrometheusPriceEncryptor
568             extends PrometheusDecimalEncryptor {
569         @Override
570         public Object parseValue(final OceanusDataFormatter pFormatter,
571                                  final byte[] pBytes) throws OceanusException {
572             return new OceanusPrice(pBytes);
573         }
574     }
575 
576     /**
577      * RatioEncryptor.
578      */
579     private static final class PrometheusRatioEncryptor
580             extends PrometheusDecimalEncryptor {
581         @Override
582         public Object parseValue(final OceanusDataFormatter pFormatter,
583                                  final byte[] pBytes) throws OceanusException {
584             return new OceanusRatio(pBytes);
585         }
586     }
587 
588     /**
589      * UnitsEncryptor.
590      */
591     private static final class PrometheusUnitsEncryptor
592             extends PrometheusDecimalEncryptor {
593         @Override
594         public Object parseValue(final OceanusDataFormatter pFormatter,
595                                  final byte[] pBytes) throws OceanusException {
596             return new OceanusUnits(pBytes);
597         }
598     }
599 
600     /**
601      * RateEncryptor.
602      */
603     private static final class PrometheusRateEncryptor
604             extends PrometheusDecimalEncryptor {
605         @Override
606         public Object parseValue(final OceanusDataFormatter pFormatter,
607                                  final byte[] pBytes) throws OceanusException {
608             return new OceanusRate(pBytes);
609         }
610     }
611 }