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.databind;
028
029import gov.nist.secauto.metaschema.core.datatype.DataTypeService;
030import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter;
031import gov.nist.secauto.metaschema.core.model.IModule;
032import gov.nist.secauto.metaschema.core.model.constraint.IConstraintSet;
033import gov.nist.secauto.metaschema.core.util.CollectionUtil;
034import gov.nist.secauto.metaschema.core.util.ObjectUtils;
035import gov.nist.secauto.metaschema.databind.io.BindingException;
036import gov.nist.secauto.metaschema.databind.io.DefaultBoundLoader;
037import gov.nist.secauto.metaschema.databind.io.Format;
038import gov.nist.secauto.metaschema.databind.io.IBoundLoader;
039import gov.nist.secauto.metaschema.databind.io.IDeserializer;
040import gov.nist.secauto.metaschema.databind.io.ISerializer;
041import gov.nist.secauto.metaschema.databind.io.json.DefaultJsonDeserializer;
042import gov.nist.secauto.metaschema.databind.io.json.DefaultJsonSerializer;
043import gov.nist.secauto.metaschema.databind.io.xml.DefaultXmlDeserializer;
044import gov.nist.secauto.metaschema.databind.io.xml.DefaultXmlSerializer;
045import gov.nist.secauto.metaschema.databind.io.yaml.DefaultYamlDeserializer;
046import gov.nist.secauto.metaschema.databind.io.yaml.DefaultYamlSerializer;
047import gov.nist.secauto.metaschema.databind.model.IAssemblyClassBinding;
048import gov.nist.secauto.metaschema.databind.model.IClassBinding;
049
050import java.util.LinkedList;
051import java.util.List;
052import java.util.Map;
053import java.util.Objects;
054import java.util.Set;
055
056import javax.xml.namespace.QName;
057
058import edu.umd.cs.findbugs.annotations.NonNull;
059
060/**
061 * The implementation of a {@link IBindingContext} provided by this library.
062 * <p>
063 * This implementation caches Module information, which can dramatically improve
064 * read and write performance at the cost of some memory use. Thus, using the
065 * same singleton of this class across multiple I/O operations will improve
066 * overall read and write performance when processing the same types of data.
067 * <p>
068 * Serializers and deserializers provided by this class using the
069 * {@link #newSerializer(Format, Class)} and
070 * {@link #newDeserializer(Format, Class)} methods will
071 * <p>
072 * This class is synchronized and is thread-safe.
073 */
074public class DefaultBindingContext implements IBindingContext {
075  private static DefaultBindingContext singleton;
076  @NonNull
077  private final IModuleLoaderStrategy moduleLoaderStrategy;
078  @NonNull
079  private final List<IBindingMatcher> bindingMatchers = new LinkedList<>();
080
081  /**
082   * Get the singleton instance of this binding context.
083   *
084   * @return the binding context
085   */
086  @NonNull
087  public static DefaultBindingContext instance() {
088    synchronized (DefaultBindingContext.class) {
089      if (singleton == null) {
090        singleton = new DefaultBindingContext();
091      }
092    }
093    return ObjectUtils.notNull(singleton);
094  }
095
096  /**
097   * Construct a new binding context.
098   *
099   * @param externalConstraintSets
100   *          the set of external constraints to configure this binding to use
101   */
102  public DefaultBindingContext(@NonNull Set<IConstraintSet> externalConstraintSets) {
103    // only allow extended classes
104    moduleLoaderStrategy = new ExternalConstraintsModuleLoaderStrategy(this, externalConstraintSets);
105  }
106
107  /**
108   * Construct a new binding context.
109   */
110  public DefaultBindingContext() {
111    // only allow extended classes
112    moduleLoaderStrategy = new SimpleModuleLoaderStrategy(this);
113  }
114
115  @Override
116  public IModule getModuleByClass(@NonNull Class<? extends IModule> clazz) {
117    return moduleLoaderStrategy.getModuleByClass(clazz);
118  }
119
120  @Override
121  public IClassBinding getClassBinding(@NonNull Class<?> clazz) {
122    return moduleLoaderStrategy.getClassBinding(clazz);
123  }
124
125  @Override
126  public Map<Class<?>, IClassBinding> getClassBindingsByClass() {
127    return moduleLoaderStrategy.getClassBindingsByClass();
128  }
129
130  @Override
131  public <TYPE extends IDataTypeAdapter<?>> TYPE getJavaTypeAdapterInstance(@NonNull Class<TYPE> clazz) {
132    return DataTypeService.getInstance().getJavaTypeAdapterByClass(clazz);
133  }
134
135  /**
136   * {@inheritDoc}
137   * <p>
138   * A serializer returned by this method is thread-safe.
139   */
140  @Override
141  public <CLASS> ISerializer<CLASS> newSerializer(@NonNull Format format, @NonNull Class<CLASS> clazz) {
142    Objects.requireNonNull(format, "format");
143    IAssemblyClassBinding classBinding = (IAssemblyClassBinding) getClassBinding(clazz);
144    if (classBinding == null) {
145      throw new IllegalStateException(String.format("Class '%s' is not bound", clazz.getClass().getName()));
146    }
147    ISerializer<CLASS> retval;
148    switch (format) {
149    case JSON:
150      retval = new DefaultJsonSerializer<>(classBinding);
151      break;
152    case XML:
153      retval = new DefaultXmlSerializer<>(classBinding);
154      break;
155    case YAML:
156      retval = new DefaultYamlSerializer<>(classBinding);
157      break;
158    default:
159      throw new UnsupportedOperationException(String.format("Unsupported format '%s'", format));
160    }
161    return retval;
162  }
163
164  /**
165   * {@inheritDoc}
166   * <p>
167   * A deserializer returned by this method is thread-safe.
168   */
169  @Override
170  public <CLASS> IDeserializer<CLASS> newDeserializer(@NonNull Format format, @NonNull Class<CLASS> clazz) {
171    IAssemblyClassBinding classBinding = (IAssemblyClassBinding) getClassBinding(clazz);
172    if (classBinding == null) {
173      throw new IllegalStateException(String.format("Class '%s' is not bound", clazz.getName()));
174    }
175    IDeserializer<CLASS> retval;
176    switch (format) {
177    case JSON:
178      retval = new DefaultJsonDeserializer<>(classBinding);
179      break;
180    case XML:
181      retval = new DefaultXmlDeserializer<>(classBinding);
182      break;
183    case YAML:
184      retval = new DefaultYamlDeserializer<>(classBinding);
185      break;
186    default:
187      throw new UnsupportedOperationException(String.format("Unsupported format '%s'", format));
188    }
189
190    return retval;
191  }
192
193  @Override
194  public DefaultBindingContext registerBindingMatcher(@NonNull IBindingMatcher matcher) {
195    synchronized (this) {
196      bindingMatchers.add(matcher);
197    }
198    return this;
199  }
200
201  /**
202   * Get the binding matchers that are associated with this class.
203   *
204   * @return the list of matchers
205   * @see #registerBindingMatcher(IBindingMatcher)
206   */
207  @NonNull
208  protected List<? extends IBindingMatcher> getBindingMatchers() {
209    synchronized (this) {
210      return CollectionUtil.unmodifiableList(bindingMatchers);
211    }
212  }
213
214  @Override
215  public Class<?> getBoundClassForXmlQName(@NonNull QName rootQName) {
216    Class<?> retval = null;
217    for (IBindingMatcher matcher : getBindingMatchers()) {
218      retval = matcher.getBoundClassForXmlQName(rootQName);
219      if (retval != null) {
220        break;
221      }
222    }
223    return retval;
224  }
225
226  @Override
227  public Class<?> getBoundClassForJsonName(@NonNull String rootName) {
228    Class<?> retval = null;
229    for (IBindingMatcher matcher : getBindingMatchers()) {
230      retval = matcher.getBoundClassForJsonName(rootName);
231      if (retval != null) {
232        break;
233      }
234    }
235    return retval;
236  }
237
238  @Override
239  public IBoundLoader newBoundLoader() {
240    return new DefaultBoundLoader(this);
241  }
242
243  @Override
244  public <CLASS> CLASS copyBoundObject(@NonNull CLASS other, Object parentInstance) throws BindingException {
245    IClassBinding classBinding = getClassBinding(other.getClass());
246    if (classBinding == null) {
247      throw new IllegalStateException(String.format("Class '%s' is not bound", other.getClass().getName()));
248    }
249    @SuppressWarnings("unchecked") CLASS retval = (CLASS) classBinding.copyBoundObject(other, parentInstance);
250    return retval;
251  }
252}