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;
017
018import java.io.InputStream;
019import java.util.ArrayList;
020import java.util.List;
021import java.util.Objects;
022import java.util.ServiceLoader;
023import java.util.stream.StreamSupport;
024import org.modeshape.schematic.document.Document;
025import org.modeshape.schematic.document.Json;
026import org.modeshape.schematic.document.ParsingException;
027import org.modeshape.schematic.internal.InMemorySchemaLibrary;
028
029public class Schematic extends DocumentFactory {
030    
031    public static final String TYPE_FIELD = "type";
032    
033    /**
034     * Returns a DB with reads the given input stream as a configuration document {@code Document}. This document is 
035     * expected to contain a {@link Schematic#TYPE_FIELD type field} to indicate the type
036     * of DB.
037     * 
038     * @see #getDb(Document, ClassLoader)
039     * @throws ParsingException if the given input stream is not a valid JSON document
040     */
041    public static <T extends SchematicDb> T getDb(InputStream configInputStream) throws ParsingException, RuntimeException {
042        Document document = Json.read(configInputStream).withVariablesReplacedWithSystemProperties();
043        return getDb(document, Schematic.class.getClassLoader());
044    }
045     
046    /**
047     * Returns a DB with the given configuration document {@code Document}. This document is expected to contain 
048     * a {@link Schematic#TYPE_FIELD type field} to indicate the type of DB.
049     * 
050     * @see #getDb(Document, ClassLoader)
051     */
052    public static <T extends SchematicDb> T getDb(Document document) throws RuntimeException {
053        return getDb(document, Schematic.class.getClassLoader());
054    }
055    
056    /**
057     * Returns a DB with the given type and configuration document, by delegating to all the available {@link SchematicDbProvider} 
058     * services. This document is expected to contain a {@link Schematic#TYPE_FIELD type field} to indicate the type
059     *
060     * @param document a {@link Document} containing the configuration of a particular DB type; may not be null
061     * @param cl a {@link ClassLoader} instance to be used when searching for available DB provider.
062     * @return a {@link SchematicDb} instance with the given alias, never {@code null}
063     * @throws RuntimeException if a DB with the given alias cannot be found or it fails during initialization 
064     */
065    @SuppressWarnings( { "unchecked", "rawtypes" } )
066    public static <T extends SchematicDb> T getDb(Document document, ClassLoader cl) throws RuntimeException {
067        String type = document.getString(TYPE_FIELD);
068        if (type == null) {
069            throw new IllegalArgumentException("The configuration document '" + document + "' does not contain a '" + TYPE_FIELD + "' field");
070        }
071        ServiceLoader<SchematicDbProvider> providers = ServiceLoader.load(SchematicDbProvider.class, cl);
072        List<RuntimeException> raisedExceptions = new ArrayList<>();
073        return (T) StreamSupport.stream(providers.spliterator(), false)
074                                .map(provider -> getDbFromProvider(type, document, raisedExceptions, provider))
075                                .filter(Objects::nonNull)
076                                .findFirst()
077                                .orElseThrow(() -> {
078                                    if (!raisedExceptions.isEmpty()) {
079                                        return raisedExceptions.get(0);
080                                    } else {
081                                        return new RuntimeException(
082                                                "None of the existing persistence providers could return a Schematic DB with type '" + type + "'");
083                                    }
084                                });
085    }
086
087    private static SchematicDb getDbFromProvider(String alias, Document document,
088                                                               List<RuntimeException> raisedExceptions,
089                                                               SchematicDbProvider<?> provider) {
090        try {
091            return provider.getDB(alias, document);
092        } catch (RuntimeException re) {
093            raisedExceptions.add(re);
094            return null;
095        } catch (Exception e) {
096            raisedExceptions.add(new RuntimeException(e));
097            return null;
098        }
099    }
100
101    /**
102     * Create an in-memory schema library.
103     *
104     * @return the empty, in-memory schema library
105     */
106    public static SchemaLibrary createSchemaLibrary() {
107        return new InMemorySchemaLibrary("In-memory schema library");
108    }
109
110    /**
111     * Create an in-memory schema library.
112     *
113     * @param name the name of the library; may be null if a default name is to be used
114     * @return the empty, in-memory schema library
115     */
116    public static SchemaLibrary createSchemaLibrary( String name ) {
117        return new InMemorySchemaLibrary(name != null ? name : "In-memory schema library");
118    }
119}