001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.bean.jsonschema;
018
019import static org.apache.juneau.common.utils.StringUtils.*;
020import static org.apache.juneau.common.utils.ThrowableUtils.*;
021
022import java.io.*;
023import java.net.*;
024import java.util.concurrent.*;
025
026import org.apache.juneau.*;
027import org.apache.juneau.json.*;
028
029/**
030 * A container for retrieving JSON {@link JsonSchema} objects by URI.
031 *
032 * <p>
033 * Subclasses must implement one of the following methods to load schemas from external sources:
034 * <ul class='spaced-list'>
035 *    <li>
036 *       {@link #getReader(URI)} - If schemas should be loaded from readers and automatically parsed.
037 *    <li>
038 *       {@link #load(URI)} - If you want control over construction of {@link JsonSchema} objects.
039 * </ul>
040 *
041 * @serial exclude
042 */
043public abstract class JsonSchemaMap extends ConcurrentHashMap<URI,JsonSchema> {
044
045   private static final long serialVersionUID = 1L;
046
047   /**
048    * Return the {@link JsonSchema} object at the specified URI.
049    *
050    * <p>
051    * If this schema object has not been loaded yet, calls {@link #load(URI)}.
052    *
053    * <p>
054    * The value can be of any of the following types: {@link URI}, {@link URL}, {@link String}.
055    * Strings must be valid URIs.
056    *
057    * <p>
058    * URIs defined by {@link UriResolver} can be used for values.
059    *
060    * @param uri The URI of the schema to retrieve.
061    * @return The JsonSchema, or <jk>null</jk> if schema was not located and could not be loaded.
062    */
063   @Override /* Map */
064   public JsonSchema get(Object uri) {
065      var u = toURI(uri);
066      var s = super.get(u);
067      if (s != null)
068         return s;
069      synchronized(this) {
070         s = load(u);
071         if (s != null) {
072            // Note:  Can't use add(Schema...) since the ID property may not be set.
073            s.setSchemaMap(this);
074            put(u, s);
075         }
076         return s;
077      }
078   }
079
080   /**
081    * Convenience method for pre-populating this map with the specified schemas.
082    *
083    * <p>
084    * The schemas passed in through this method MUST have their ID properties set.
085    *
086    * @param schemas The set of schemas to add to this map.
087    * @return This object.
088    * @throws RuntimeException If one or more schema objects did not have their ID property set.
089    */
090   public JsonSchemaMap add(JsonSchema...schemas) {
091      for (var schema : schemas) {
092         if (schema.getId() == null)
093            throw new IllegalArgumentException("Schema with no ID passed to JsonSchemaMap.add(Schema...)");
094         put(schema.getId(), schema);
095         schema.setSchemaMap(this);
096      }
097      return this;
098   }
099
100   /**
101    * Subclasses must implement either this method or {@link #getReader(URI)} to load the schema with the specified URI.
102    *
103    * <p>
104    * It's up to the implementer to decide where these come from.
105    *
106    * <p>
107    * The default implementation calls {@link #getReader(URI)} and parses the schema document.
108    * If {@link #getReader(URI)} returns <jk>null</jk>, this method returns <jk>null</jk> indicating this is an
109    * unreachable document.
110    *
111    * @param uri The URI to load the schema from.
112    * @return The parsed schema.
113    */
114   public JsonSchema load(URI uri) {
115      try (var r = getReader(uri)) {
116         if (r == null)
117            return null;
118         return JsonParser.DEFAULT.parse(r, JsonSchema.class);
119      } catch (Exception e) {
120         throw asRuntimeException(e);
121      }
122   }
123
124   /**
125    * Subclasses must implement either this method or {@link #load(URI)} to load the schema with the specified URI.
126    *
127    * <p>
128    * It's up to the implementer to decide where these come from.
129    *
130    * <p>
131    * The default implementation returns <jk>null</jk>.
132    *
133    * @param uri The URI to connect to and retrieve the contents.
134    * @return The reader from reading the specified URI.
135    */
136   public Reader getReader(URI uri) {
137      return null;
138   }
139}