001/*
002 * ModeShape (http://www.modeshape.org)
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *       http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.modeshape.schematic.internal.schema;
017
018import java.io.Serializable;
019import java.net.URI;
020import java.net.URISyntaxException;
021import java.util.Map;
022import java.util.concurrent.ConcurrentHashMap;
023import org.modeshape.schematic.DocumentLibrary;
024import org.modeshape.schematic.annotation.ThreadSafe;
025import org.modeshape.schematic.document.Document;
026import org.modeshape.schematic.document.JsonSchema;
027import org.modeshape.schematic.document.Path;
028import org.modeshape.schematic.internal.document.Paths;
029
030/**
031 * A cache of {@link SchemaDocument} instances that each have a {@link SchemaDocument#getValidator() validator} that can
032 * {@link Validator#validate(Object, String, Document, Path, Problems, Validator.SchemaDocumentResolver)
033 * validate} JSON Documents. When a SchemaDocument is needed and has not yet been loaded, this cache finds the corresponding JSON
034 * Schema {@link Document documents} from the {@link DocumentLibrary}, and uses a {@link Validator.Factory factory} to create the
035 * {@link Validator}.
036 * 
037 * @author Randall Hauch <rhauch@redhat.com> (C) 2011 Red Hat Inc.
038 */
039@ThreadSafe
040public class SchemaDocumentCache implements Validator.SchemaDocumentResolver, Serializable {
041
042    private static final long serialVersionUID = 1L;
043
044    private final String defaultMetaSchemaUri;
045    private final DocumentLibrary jsonSchemaDocuments;
046    private transient Map<String, SchemaDocument> schemaDocumentsByUri = new ConcurrentHashMap<>();
047
048    public SchemaDocumentCache( DocumentLibrary jsonSchemaDocuments,
049                                String defaultMetaSchemaUri ) {
050        this.jsonSchemaDocuments = jsonSchemaDocuments;
051        this.defaultMetaSchemaUri = defaultMetaSchemaUri != null ? defaultMetaSchemaUri : JsonSchema.Version.Latest.CORE_METASCHEMA_URL;
052    }
053
054    /**
055     * Find the JSON Schema document with the supplied URI, and build the {@link SchemaDocument in-memory representation}
056     * (including the {@link Validator validator}.
057     * 
058     * @param uri the URI of the schema being read
059     * @param problems the object for recording any problems encountered while loading the schema
060     * @return the in-memory {@link Document} representation, or null if there were fatal errors
061     */
062    @Override
063    public SchemaDocument get( String uri,
064                               Problems problems ) {
065        if (uri == null) {
066            throw new IllegalArgumentException("The 'uri' parameter may not be null");
067        }
068
069        // Check the cache first ...
070        SchemaDocument result = schemaDocumentsByUri.get(uri);
071        if (result != null) {
072            return result;
073        }
074
075        // Find the JSON Schema document ...
076        Document doc = jsonSchemaDocuments.get(uri);
077        if (doc == null) {
078            problems.recordError(Paths.rootPath(), "Unable to find the JSON Schema document for '" + uri + "'");
079            return null;
080        }
081
082        // Validate the JSON document, if required ...
083        String id = doc.getString("id");
084        String schemaRef = doc.getString("$schema");
085        if (schemaRef == null) {
086            schemaRef = defaultMetaSchemaUri;
087        }
088        if (!schemaRef.equals(id)) {
089            // This is not the meta-schema, so we need to validate it ...
090            SchemaDocument schemaOfSchema = get(schemaRef, problems);
091            Document schemaOfSchemaDoc = schemaOfSchema.getDocument();
092            schemaOfSchema.getValidator().validate(null, null, schemaOfSchemaDoc, Paths.rootPath(), problems, this);
093        }
094
095        // The schema was valid ...
096        URI schemaRefUri = null;
097        try {
098            schemaRefUri = new URI(uri);
099        } catch (URISyntaxException e) {
100            problems.recordWarning(Paths.path("$schema"), "The URI of the referenced schema '" + uri + "' is not a valid URI");
101        }
102
103        // Now build a validator for this JSON Schema (if we can) ...
104        Validator validator = createFactory(schemaRefUri, problems).create(doc, Paths.rootPath());
105        if (validator != null) {
106            // Create the schema representation and cache it ...
107            result = new SchemaDocument(schemaRef, doc, validator);
108            schemaDocumentsByUri.put(uri, result);
109        }
110
111        return result;
112    }
113
114    /**
115     * Remove any {@link SchemaDocument} that was loaded into this cache.
116     * 
117     * @param uri the URI of the schema being read
118     * @return true if a SchemaDocument was removed from the cache, or false if this cache did not contain a SchemaDocument for
119     *         the supplied URI
120     */
121    public boolean remove( String uri ) {
122        return schemaDocumentsByUri.remove(uri) != null;
123    }
124
125    /**
126     * Remove all {@link SchemaDocument} loaded into this cache.
127     */
128    public void removeAll() {
129        schemaDocumentsByUri.clear();
130    }
131
132    protected Validator.Factory createFactory( URI schemaRefUri,
133                                               Problems problems ) {
134        return new JsonSchemaValidatorFactory(schemaRefUri, problems);
135    }
136}