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