001/*
002 * PermissionsEx
003 * Copyright (C) zml and PermissionsEx contributors
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * 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 ca.stellardrift.permissionsex.impl.config;
018
019import ca.stellardrift.permissionsex.datastore.ProtoDataStore;
020import ca.stellardrift.permissionsex.exception.PEBKACException;
021import ca.stellardrift.permissionsex.exception.PermissionsException;
022import ca.stellardrift.permissionsex.impl.util.PCollections;
023import io.leangen.geantyref.TypeFactory;
024import io.leangen.geantyref.TypeToken;
025import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
026import org.pcollections.PSet;
027import org.pcollections.PStack;
028import org.pcollections.PVector;
029import org.spongepowered.configurate.ConfigurateException;
030import org.spongepowered.configurate.ConfigurationNode;
031import org.spongepowered.configurate.ConfigurationOptions;
032import org.spongepowered.configurate.hocon.HoconConfigurationLoader;
033import org.spongepowered.configurate.loader.ConfigurationLoader;
034import org.spongepowered.configurate.objectmapping.ConfigSerializable;
035import org.spongepowered.configurate.objectmapping.ObjectMapper;
036import org.spongepowered.configurate.objectmapping.meta.NodeResolver;
037import org.spongepowered.configurate.objectmapping.meta.Setting;
038import org.spongepowered.configurate.serialize.SerializationException;
039import org.spongepowered.configurate.serialize.TypeSerializerCollection;
040import org.spongepowered.configurate.util.CheckedSupplier;
041
042import java.io.IOException;
043import java.net.URL;
044import java.util.Collections;
045import java.util.List;
046import java.util.Map;
047
048
049/**
050 * Configuration for PermissionsEx.
051 *
052 * <p>This is designed to be serialized with a Configurate {@link ObjectMapper}.</p>
053 */
054@ConfigSerializable
055public final class FilePermissionsExConfiguration<T> implements PermissionsExConfiguration<T> {
056
057    private static final TypeSerializerCollection PEX_SERIALIZERS = populateSerializers(TypeSerializerCollection.defaults().childBuilder()).build();
058    public static final ConfigurationOptions PEX_OPTIONS = ConfigurationOptions.defaults()
059            .implicitInitialization(true)
060            .shouldCopyDefaults(true)
061            .serializers(PEX_SERIALIZERS);
062
063    public static ConfigurationOptions decorateOptions(final ConfigurationOptions input) {
064        return input
065                .implicitInitialization(true)
066                .shouldCopyDefaults(true)
067                .serializers(PEX_SERIALIZERS);
068    }
069
070    private final ConfigurationLoader<?> loader;
071    private final ConfigurationNode node;
072    private final Class<T> platformConfigClass;
073    private @MonotonicNonNull Instance<T> instance;
074
075    @ConfigSerializable
076    static class Instance<T> {
077        @Setting
078        private Map<String, ProtoDataStore<?>> backends;
079        @Setting
080        private String defaultBackend;
081        @Setting
082        private boolean debug;
083        @Setting
084        private List<String> serverTags;
085
086        T platform;
087
088        void validate() throws PEBKACException {
089            if (this.backends.isEmpty()) {
090                throw new PEBKACException(Messages.CONFIG_ERROR_NO_BACKENDS.tr());
091            }
092            if (this.defaultBackend == null) {
093                throw new PEBKACException(Messages.CONFIG_ERROR_NO_DEFAULT.tr());
094            }
095
096            if (!this.backends.containsKey(this.defaultBackend)) {
097                throw new PEBKACException(Messages.CONFIG_ERROR_INVALID_DEFAULT.tr(defaultBackend, backends.keySet()));
098            }
099        }
100    }
101
102    FilePermissionsExConfiguration(ConfigurationLoader<?> loader, ConfigurationNode node, Class<T> platformConfigClass) {
103        this.loader = loader;
104        this.node = node;
105        this.platformConfigClass = platformConfigClass;
106    }
107
108    public static FilePermissionsExConfiguration<?> fromLoader(ConfigurationLoader<?> loader) throws ConfigurateException {
109        return fromLoader(loader, Void.class);
110    }
111
112    /**
113     * Register PEX's type serializers with the provided collection
114     *
115     * @param coll The collection to add to
116     * @return provided collection
117     */
118    public static TypeSerializerCollection.Builder populateSerializers(TypeSerializerCollection.Builder coll) {
119        return coll
120                // Collection types
121                .register(new TypeToken<PVector<?>>() {}, new PCollectionSerializer<>(PCollections::vector))
122                .register(new TypeToken<PSet<?>>() {}, new PCollectionSerializer<>(PCollections::set))
123                .register(new TypeToken<PStack<?>>() {}, new PCollectionSerializer<>(PCollections::stack))
124                .register(PMapSerializer.TYPE, PMapSerializer.INSTANCE)
125                // PEX's own types
126                .register(new TypeToken<ProtoDataStore<?>>() {}, new ProtoDataStoreSerializer())
127                .register(new TypeToken<CheckedSupplier<?, SerializationException>>() {}, SupplierSerializer.INSTANCE)
128                .registerAnnotatedObjects(ObjectMapper.factoryBuilder()
129                        .addNodeResolver(NodeResolver.onlyWithSetting())
130                        .build());
131    }
132
133    public static <T> FilePermissionsExConfiguration<T> fromLoader(ConfigurationLoader<?> loader, Class<T> platformConfigClass) throws ConfigurateException {
134        ConfigurationNode node = loader.load(loader.defaultOptions().serializers(PEX_SERIALIZERS).implicitInitialization(true).shouldCopyDefaults(true));
135        ConfigurationNode fallbackConfig;
136        try {
137            fallbackConfig = FilePermissionsExConfiguration.loadDefaultConfiguration();
138        } catch (final ConfigurateException e) {
139            throw new Error("PEX's default configuration could not be loaded!", e);
140        }
141        ConfigTransformations.versions().apply(node);
142        node.mergeFrom(fallbackConfig);
143        ConfigurationNode defBackendNode = node.node("default-backend");
144        if (defBackendNode.empty()) { // Set based on whether or not the H2 backend is available
145            defBackendNode.set("default-file");
146            /*try {
147                Class.forName("org.h2.Driver");
148                defBackendNode.setValue("default");
149            } catch (ClassNotFoundException e) {
150                defBackendNode.setValue("default-file");
151            }*/
152        }
153
154        FilePermissionsExConfiguration<T> config = new FilePermissionsExConfiguration<>(loader, node, platformConfigClass);
155        config.load();
156        return config;
157    }
158
159    @SuppressWarnings("unchecked") // manual type checking
160    private void load() throws ConfigurateException {
161        this.instance = (Instance<T>) this.node.get(TypeFactory.parameterizedClass(Instance.class, this.platformConfigClass));
162        if (this.platformConfigClass == Void.class) {
163            this.instance.platform = null;
164        } else {
165            this.instance.platform = this.platformConfigNode().get(this.platformConfigClass);
166        }
167        this.loader.save(node);
168    }
169
170    @Override
171    public void save() throws IOException {
172        this.node.set(TypeFactory.parameterizedClass(Instance.class, this.platformConfigClass), this.instance);
173        if (this.platformConfigClass != Void.class) {
174            platformConfigNode().set(this.platformConfigClass, this.instance.platform);
175        }
176
177        this.loader.save(node);
178    }
179
180    private ConfigurationNode platformConfigNode() {
181        return this.node.node("platform");
182    }
183
184    @Override
185    public ProtoDataStore<?> getDataStore(String name) {
186        return this.instance.backends.get(name);
187    }
188
189    @Override
190    public ProtoDataStore<?> getDefaultDataStore() {
191        return this.instance.backends.get(this.instance.defaultBackend);
192    }
193
194    @Override
195    public boolean isDebugEnabled() {
196        return this.instance.debug;
197    }
198
199    @Override
200    public List<String> getServerTags() {
201        return Collections.unmodifiableList(this.instance.serverTags);
202    }
203
204    @Override
205    public void validate() throws PEBKACException {
206        this.instance.validate();
207    }
208
209    @Override
210    public T getPlatformConfig() {
211        return this.instance.platform;
212    }
213
214    @Override
215    public FilePermissionsExConfiguration<T> reload() throws IOException {
216        try {
217            ConfigurationNode node = this.loader.load();
218            FilePermissionsExConfiguration<T> ret = new FilePermissionsExConfiguration<>(this.loader, node, this.platformConfigClass);
219            ret.load();
220            return ret;
221        } catch (final ConfigurateException ex) {
222            throw new IOException(ex);
223        }
224    }
225
226    public static ConfigurationNode loadDefaultConfiguration() throws ConfigurateException {
227        final URL defaultConfig = FilePermissionsExConfiguration.class.getResource("default.conf");
228        if (defaultConfig == null) {
229            throw new Error(new PermissionsException(Messages.CONFIG_ERROR_DEFAULT_CONFIG.tr()));
230        }
231        HoconConfigurationLoader fallbackLoader = HoconConfigurationLoader.builder()
232                .defaultOptions(FilePermissionsExConfiguration::decorateOptions)
233                .url(defaultConfig).build();
234        return fallbackLoader.load();
235    }
236}