001/*
002 * Portions of this software was developed by employees of the National Institute
003 * of Standards and Technology (NIST), an agency of the Federal Government and is
004 * being made available as a public service. Pursuant to title 17 United States
005 * Code Section 105, works of NIST employees are not subject to copyright
006 * protection in the United States. This software may be subject to foreign
007 * copyright. Permission in the United States and in foreign countries, to the
008 * extent that NIST may hold copyright, to use, copy, modify, create derivative
009 * works, and distribute this software and its documentation without fee is hereby
010 * granted on a non-exclusive basis, provided that this notice and disclaimer
011 * of warranty appears in all copies.
012 *
013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
025 */
026
027package gov.nist.secauto.metaschema.core.datatype;
028
029import gov.nist.secauto.metaschema.core.util.CustomCollectors;
030
031import org.apache.logging.log4j.LogManager;
032import org.apache.logging.log4j.Logger;
033
034import java.util.List;
035import java.util.Map;
036import java.util.ServiceLoader;
037import java.util.ServiceLoader.Provider;
038import java.util.concurrent.ConcurrentHashMap;
039import java.util.function.Function;
040import java.util.stream.Collectors;
041
042import edu.umd.cs.findbugs.annotations.NonNull;
043import edu.umd.cs.findbugs.annotations.Nullable;
044import nl.talsmasoftware.lazy4j.Lazy;
045
046/**
047 * This class provides a singleton service to allow data types to be discovered
048 * within the system based on an SPI provided by {@link IDataTypeProvider}.
049 */
050public final class DataTypeService {
051  private static final Logger LOGGER = LogManager.getLogger(DataTypeService.class);
052  private static final Lazy<DataTypeService> INSTANCE = Lazy.lazy(() -> new DataTypeService());
053
054  private Map<String, IDataTypeAdapter<?>> libraryByName;
055  private Map<Class<? extends IDataTypeAdapter<?>>, IDataTypeAdapter<?>> libraryByClass;
056
057  /**
058   * Get the singleton service instance, which will be lazy constructed on first
059   * access.
060   *
061   * @return the service instance
062   */
063  @SuppressWarnings("null")
064  @NonNull
065  public static DataTypeService getInstance() {
066    return INSTANCE.get();
067  }
068
069  private DataTypeService() {
070    load();
071  }
072
073  /**
074   * Lookup a specific {@link IDataTypeAdapter} instance by its name.
075   *
076   * @param name
077   *          the data type name of data type adapter to get the instance for
078   * @return the instance or {@code null} if the instance is unknown to the type
079   *         system
080   */
081  @Nullable
082  public IDataTypeAdapter<?> getJavaTypeAdapterByName(@NonNull String name) {
083    return libraryByName.get(name);
084  }
085
086  /**
087   * Lookup a specific {@link IDataTypeAdapter} instance by its class.
088   *
089   * @param clazz
090   *          the adapter class to get the instance for
091   * @param <TYPE>
092   *          the type of the requested adapter
093   * @return the instance or {@code null} if the instance is unknown to the type
094   *         system
095   */
096  @SuppressWarnings("unchecked")
097  @Nullable
098  public <TYPE extends IDataTypeAdapter<?>> TYPE getJavaTypeAdapterByClass(@NonNull Class<TYPE> clazz) {
099    return (TYPE) libraryByClass.get(clazz);
100  }
101
102  /**
103   * Load available data types registered with the {@link IDataTypeProvider} SPI.
104   *
105   * @throws IllegalStateException
106   *           if there are two adapters with the same name
107   */
108  private void load() {
109    ServiceLoader<IDataTypeProvider> loader = ServiceLoader.load(IDataTypeProvider.class);
110    List<IDataTypeAdapter<?>> dataTypes = loader.stream()
111        .map(Provider<IDataTypeProvider>::get)
112        .flatMap(provider -> provider.getJavaTypeAdapters().stream())
113        .collect(Collectors.toList());
114
115    Map<String, IDataTypeAdapter<?>> libraryByName = dataTypes.stream()
116        .flatMap(dataType -> dataType.getNames().stream()
117            .map(name -> Map.entry(name, dataType)))
118        .collect(CustomCollectors.toMap(
119            Map.Entry::getKey,
120            (entry) -> entry.getValue(),
121            (key, v1, v2) -> {
122              if (LOGGER.isWarnEnabled()) {
123                LOGGER.warn("Data types '{}' and '{}' have duplicate name '{}'. Using the first.",
124                    v1.getClass().getName(),
125                    v2.getClass().getName(),
126                    key);
127              }
128              return v1;
129            },
130            ConcurrentHashMap::new));
131
132    @SuppressWarnings({ "unchecked", "null" }) Map<Class<? extends IDataTypeAdapter<?>>,
133        IDataTypeAdapter<?>> libraryByClass = dataTypes.stream()
134            .collect(CustomCollectors.toMap(
135                dataType -> (Class<? extends IDataTypeAdapter<?>>) dataType.getClass(),
136                Function.identity(),
137                (key, v1, v2) -> {
138                  if (LOGGER.isWarnEnabled()) {
139                    LOGGER.warn("Duplicate data type class '{}'. Using the first.",
140                        key.getClass().getName());
141                  }
142                  return v1;
143                },
144                ConcurrentHashMap::new));
145
146    synchronized (this) {
147      this.libraryByName = libraryByName;
148      this.libraryByClass = libraryByClass;
149    }
150  }
151}