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}