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.backend.file; 018 019import ca.stellardrift.permissionsex.PermissionsEngine; 020import ca.stellardrift.permissionsex.legacy.LegacyConversions; 021import ca.stellardrift.permissionsex.logging.FormattedLogger; 022import org.spongepowered.configurate.ConfigurationNode; 023import org.spongepowered.configurate.ScopedConfigurationNode; 024import org.spongepowered.configurate.transformation.ConfigurationTransformation; 025import org.spongepowered.configurate.transformation.TransformAction; 026 027import java.util.ArrayList; 028import java.util.Collections; 029import java.util.HashMap; 030import java.util.List; 031import java.util.Map; 032import java.util.Objects; 033 034import static org.spongepowered.configurate.NodePath.path; 035import static org.spongepowered.configurate.transformation.ConfigurationTransformation.WILDCARD_OBJECT; 036import static org.spongepowered.configurate.util.UnmodifiableCollections.immutableMapEntry; 037 038public class SchemaMigrations { 039 public static final int LATEST_VERSION = 4; 040 private SchemaMigrations() { 041 } 042 043 private static ConfigurationTransformation.Builder tBuilder() { 044 return ConfigurationTransformation.builder(); 045 } 046 047 static <N extends ScopedConfigurationNode<N>> ConfigurationTransformation versionedMigration(final FormattedLogger logger) { 048 return ConfigurationTransformation.versionedBuilder() 049 .versionKey("schema-version") 050 .addVersion(LATEST_VERSION, threeToFour()) 051 .addVersion(3, twoTo3()) 052 .addVersion(2, oneTo2(logger)) 053 .addVersion(1, initialTo1()) 054 .build(); 055 } 056 057 static ConfigurationTransformation threeToFour() { 058 return ConfigurationTransformation.chain( 059 tBuilder() 060 .addAction(path("worlds", WILDCARD_OBJECT, "inheritance"), (inputPath, valueAtPath) -> { 061 final List<String> items = valueAtPath.getList(String.class); 062 valueAtPath.raw(null); 063 items.stream() 064 .map(input -> "world:" + input) 065 .forEach(longer -> valueAtPath.appendListNode().raw(longer)); 066 067 return new Object[]{"context-inheritance", "world:" + inputPath.get(1)}; 068 }).build(), 069 tBuilder() 070 .addAction(path("worlds"), (inputPath, valueAtPath) -> { 071 valueAtPath.raw(null); 072 return null; 073 }).build()); 074 } 075 076 static ConfigurationTransformation twoTo3() { 077 final Map<String, List<Map.Entry<String, Integer>>> convertedRanks = new HashMap<>(); 078 return ConfigurationTransformation.chain( 079 tBuilder() 080 .addAction(path(WILDCARD_OBJECT), (nodePath, configurationNode) -> { 081 if (configurationNode.isMap()) { 082 String lastPath = nodePath.get(0).toString(); 083 if (lastPath.endsWith("s")) { 084 lastPath = lastPath.substring(0, lastPath.length() - 1); 085 } 086 return new Object[]{"subjects", lastPath}; 087 } else { 088 return null; 089 } 090 }).build(), 091 tBuilder() 092 .addAction(path("subjects", "group", WILDCARD_OBJECT), (nodePath, configurationNode) -> { 093 for (ConfigurationNode child : configurationNode.childrenList()) { 094 if (child.node(FileSubjectData.KEY_CONTEXTS).virtual() || child.node(FileSubjectData.KEY_CONTEXTS).childrenMap().isEmpty()) { 095 ConfigurationNode optionsNode = child.node("options"); 096 if (optionsNode.virtual()) { 097 return null; 098 } 099 ConfigurationNode rank = optionsNode.node("rank"); 100 if (!rank.virtual()) { 101 final String rankLadder = optionsNode.node("rank-ladder").getString("default"); 102 List<Map.Entry<String, Integer>> tempVals = convertedRanks.computeIfAbsent(rankLadder.toLowerCase(), k -> new ArrayList<>()); 103 tempVals.add(immutableMapEntry(configurationNode.key().toString(), rank.getInt())); 104 rank.raw(null); 105 optionsNode.node("rank-ladder").raw(null); 106 if (optionsNode.childrenMap().isEmpty()) { 107 optionsNode.raw(null); 108 } 109 } 110 111 } 112 } 113 return null; 114 }).build(), 115 tBuilder().addAction(path(), (nodePath, configurationNode) -> { 116 for (Map.Entry<String, List<Map.Entry<String, Integer>>> ent : convertedRanks.entrySet()) { 117 ent.getValue().sort((a, b) -> b.getValue().compareTo(a.getValue())); 118 ConfigurationNode ladderNode = configurationNode.node(FileDataStore.KEY_RANK_LADDERS, ent.getKey()); 119 for (Map.Entry<String, Integer> grp : ent.getValue()) { 120 ladderNode.appendListNode().set("group:" + grp.getKey()); 121 } 122 } 123 return null; 124 }).build()); 125 } 126 127 static ConfigurationTransformation oneTo2(final FormattedLogger logger) { 128 return ConfigurationTransformation.chain( 129 tBuilder() 130 .addAction(path(WILDCARD_OBJECT, WILDCARD_OBJECT), (nodePath, configurationNode) -> { 131 final ConfigurationNode src = configurationNode.copy(); 132 configurationNode.appendListNode().from(src); 133 return null; 134 }) 135 .build(), 136 tBuilder() 137 .addAction(path(WILDCARD_OBJECT, WILDCARD_OBJECT, 0, "worlds"), (nodePath, configurationNode) -> { 138 ConfigurationNode entityNode = configurationNode.parent().parent(); 139 for (Map.Entry<Object, ? extends ConfigurationNode> ent : configurationNode.childrenMap().entrySet()) { 140 entityNode.appendListNode().from(ent.getValue()) 141 .node(FileSubjectData.KEY_CONTEXTS, "world").set(ent.getKey()); 142 143 } 144 configurationNode.raw(null); 145 return null; 146 }).build(), 147 tBuilder() 148 .addAction(path(WILDCARD_OBJECT, WILDCARD_OBJECT, WILDCARD_OBJECT, "permissions"), (nodePath, configurationNode) -> { 149 List<String> existing = configurationNode.getList(String.class, Collections.emptyList()); 150 if (!existing.isEmpty()) { 151 configurationNode.raw(Collections.emptyMap()); 152 } 153 for (String permission : existing) { 154 int value = permission.startsWith("-") ? -1 : 1; 155 if (value < 0) { 156 permission = permission.substring(1); 157 } 158 if (permission.equals("*")) { 159 configurationNode.parent().node("permissions-default").set(value); 160 continue; 161 } 162 permission = LegacyConversions.convertLegacyPermission(permission); 163 if (permission.contains("*")) { 164 logger.warn(Messages.FILE_CONVERSION_ILLEGAL_CHAR.tr(configurationNode.path())); 165 } 166 configurationNode.node(permission).raw(value); 167 } 168 if (configurationNode.empty()) { 169 configurationNode.raw(null); 170 } 171 return null; 172 }) 173 .addAction(path("users", WILDCARD_OBJECT, WILDCARD_OBJECT, "group"), (nodePath, configurationNode) -> { 174 Object[] retPath = nodePath.array(); 175 retPath[retPath.length - 1] = "parents"; 176 for (ConfigurationNode child : configurationNode.childrenList()) { 177 child.set("group:" + child.getString()); 178 } 179 return retPath; 180 }) 181 .addAction(path("groups", WILDCARD_OBJECT, WILDCARD_OBJECT, "inheritance"), (nodePath, configurationNode) -> { 182 Object[] retPath = nodePath.array(); 183 retPath[retPath.length - 1] = "parents"; 184 for (ConfigurationNode child : configurationNode.childrenList()) { 185 child.set("group:" + child.getString()); 186 } 187 return retPath; 188 }) 189 .addAction(path("groups", WILDCARD_OBJECT, WILDCARD_OBJECT), (inputPath, valueAtPath) -> { 190 final ConfigurationNode defaultNode = valueAtPath.node("options", "default"); 191 if (!defaultNode.virtual()) { 192 if (defaultNode.getBoolean()) { 193 ConfigurationNode addToNode = null; 194 final ConfigurationNode defaultsParent = valueAtPath.parent().parent().parent().node("fallbacks", LegacyConversions.SUBJECTS_USER); 195 final Object contexts = valueAtPath.node(FileSubjectData.KEY_CONTEXTS).raw(); 196 for (ConfigurationNode node : defaultsParent.childrenList()) { 197 if (Objects.equals(node.node(FileSubjectData.KEY_CONTEXTS).raw(), contexts)) { 198 addToNode = node; 199 break; 200 } 201 } 202 if (addToNode == null) { 203 addToNode = defaultsParent.appendListNode(); 204 addToNode.node(FileSubjectData.KEY_CONTEXTS).set(valueAtPath.node(FileSubjectData.KEY_CONTEXTS)); 205 } 206 207 addToNode.node("parents").appendListNode().set("group:" + valueAtPath.parent().key()); 208 } 209 defaultNode.raw(null); 210 final ConfigurationNode optionsNode = valueAtPath.node("options"); 211 if (optionsNode.childrenMap().isEmpty()) { 212 optionsNode.raw(null); 213 } 214 } 215 return null; 216 }).build()); 217 } 218 219 private static final TransformAction MOVE_PREFIX_SUFFIX_ACTION = (nodePath, configurationNode) -> { 220 final ConfigurationNode prefixNode = configurationNode.node("prefix"); 221 if (!prefixNode.virtual()) { 222 configurationNode.node("options", "prefix").from(prefixNode); 223 prefixNode.set(null); 224 } 225 226 final ConfigurationNode suffixNode = configurationNode.node("suffix"); 227 if (!suffixNode.virtual()) { 228 configurationNode.node("options", "suffix").from(suffixNode); 229 suffixNode.raw(null); 230 } 231 232 final ConfigurationNode defaultNode = configurationNode.node("default"); 233 if (!defaultNode.virtual()) { 234 configurationNode.node("options", "default").from(defaultNode); 235 defaultNode.raw(null); 236 } 237 return null; 238 }; 239 240 static ConfigurationTransformation initialTo1() { 241 return ConfigurationTransformation.builder() 242 .addAction(path(WILDCARD_OBJECT, WILDCARD_OBJECT), MOVE_PREFIX_SUFFIX_ACTION) 243 .addAction(path(WILDCARD_OBJECT, WILDCARD_OBJECT, "worlds", WILDCARD_OBJECT), MOVE_PREFIX_SUFFIX_ACTION) 244 .build(); 245 } 246}