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}