ninja.leaping.permissionsex.backend.file.FileDataStore.java Source code

Java tutorial

Introduction

Here is the source code for ninja.leaping.permissionsex.backend.file.FileDataStore.java

Source

/**
 * PermissionsEx
 * Copyright (C) zml and PermissionsEx contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package ninja.leaping.permissionsex.backend.file;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListenableFutureTask;
import ninja.leaping.configurate.ConfigurationNode;
import ninja.leaping.configurate.ConfigurationOptions;
import ninja.leaping.configurate.hocon.HoconConfigurationLoader;
import ninja.leaping.configurate.json.FieldValueSeparatorStyle;
import ninja.leaping.configurate.json.JSONConfigurationLoader;
import ninja.leaping.configurate.loader.ConfigurationLoader;
import ninja.leaping.configurate.objectmapping.ObjectMappingException;
import ninja.leaping.configurate.objectmapping.Setting;
import ninja.leaping.configurate.transformation.ConfigurationTransformation;
import ninja.leaping.configurate.transformation.TransformAction;
import ninja.leaping.configurate.yaml.YAMLConfigurationLoader;
import ninja.leaping.permissionsex.backend.AbstractDataStore;
import ninja.leaping.permissionsex.backend.ConversionUtils;
import ninja.leaping.permissionsex.backend.DataStore;
import ninja.leaping.permissionsex.data.ImmutableOptionSubjectData;
import ninja.leaping.permissionsex.exception.PermissionsLoadingException;

import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicInteger;

import static ninja.leaping.configurate.transformation.ConfigurationTransformation.WILDCARD_OBJECT;
import static ninja.leaping.permissionsex.util.Translations._;

public final class FileDataStore extends AbstractDataStore {
    public static final Factory FACTORY = new Factory("file", FileDataStore.class);

    @Setting
    private String file;
    @Setting
    private boolean compat = false;

    private ConfigurationLoader permissionsFileLoader;
    private ConfigurationNode permissionsConfig;
    private final AtomicInteger saveSuppressed = new AtomicInteger();

    public FileDataStore() {
        super(FACTORY);
    }

    private static ConfigurationTransformation.Builder tBuilder() {
        return ConfigurationTransformation.builder();
    }

    private ConfigurationLoader<? extends ConfigurationNode> createLoader(File file) {
        JSONConfigurationLoader.Builder build = JSONConfigurationLoader.builder().setFile(file).setIndent(4)
                .setFieldValueSeparatorStyle(FieldValueSeparatorStyle.SPACE_AFTER);

        /*if (!compat) {
        build.getFactory().disable(JsonGenerator.Feature.QUOTE_FIELD_NAMES);
        build.getFactory().enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES);
        }*/
        return build.build();
    }

    private File migrateLegacy(File permissionsFile, String extension, ConfigurationLoader<?> loader,
            String formatName) throws PermissionsLoadingException {
        File legacyPermissionsFile = permissionsFile;
        file = file.replace(extension, ".json");
        permissionsFile = new File(getManager().getBaseDirectory(), file);
        permissionsFileLoader = createLoader(permissionsFile);
        try {
            permissionsConfig = loader.load();
            permissionsFileLoader.save(permissionsConfig);
            legacyPermissionsFile.renameTo(new File(legacyPermissionsFile.getCanonicalPath() + ".legacy-backup"));
        } catch (IOException e) {
            throw new PermissionsLoadingException(
                    _("While loading legacy %s permissions from %s", formatName, permissionsFile), e);
        }
        return permissionsFile;
    }

    @Override
    protected void initializeInternal() throws PermissionsLoadingException {
        File permissionsFile = new File(getManager().getBaseDirectory(), file);
        if (file.endsWith(".yml")) {
            permissionsFile = migrateLegacy(permissionsFile, ".yml",
                    YAMLConfigurationLoader.builder().setFile(permissionsFile).build(), "YML");
        } else if (file.endsWith(".conf")) {
            permissionsFile = migrateLegacy(permissionsFile, ".conf",
                    HoconConfigurationLoader.builder().setFile(permissionsFile).build(), "HOCON");
        } else {
            permissionsFileLoader = createLoader(permissionsFile);
        }

        try {
            permissionsConfig = permissionsFileLoader.load(ConfigurationOptions.defaults());//.setMapFactory(MapFactories.unordered()));
        } catch (IOException e) {
            throw new PermissionsLoadingException(_("While loading permissions file from %s", permissionsFile), e);
        }

        if (permissionsConfig.getChildrenMap().isEmpty()) { // New configuration, populate with default data
            try {
                performBulkOperationSync(new Function<DataStore, Void>() {
                    @Nullable
                    @Override
                    public Void apply(@Nullable DataStore input) {
                        applyDefaultData();
                        permissionsConfig.getNode("schema-version").setValue(2);
                        return null;
                    }
                });
            } catch (PermissionsLoadingException e) {
                throw e;
            } catch (Exception e) {
                throw new PermissionsLoadingException(_("Error creating initial data for file backend"), e);
            }
        } else {

            final TransformAction movePrefixSuffixDefaultAction = new TransformAction() {
                @Override
                public Object[] visitPath(ConfigurationTransformation.NodePath nodePath,
                        ConfigurationNode configurationNode) {
                    final ConfigurationNode prefixNode = configurationNode.getNode("prefix");
                    if (!prefixNode.isVirtual()) {
                        configurationNode.getNode("options", "prefix").setValue(prefixNode);
                        prefixNode.setValue(null);
                    }

                    final ConfigurationNode suffixNode = configurationNode.getNode("suffix");
                    if (!suffixNode.isVirtual()) {
                        configurationNode.getNode("options", "suffix").setValue(suffixNode);
                        suffixNode.setValue(null);
                    }

                    final ConfigurationNode defaultNode = configurationNode.getNode("default");
                    if (!defaultNode.isVirtual()) {
                        configurationNode.getNode("options", "default").setValue(defaultNode);
                        defaultNode.setValue(null);
                    }
                    return null;
                }
            };

            ConfigurationTransformation versionUpdater = ConfigurationTransformation.versionedBuilder()
                    .setVersionKey("schema-version").addVersion(2, ConfigurationTransformation.chain(tBuilder()
                            .addAction(new Object[] { WILDCARD_OBJECT, WILDCARD_OBJECT }, new TransformAction() {
                                @Override
                                public Object[] visitPath(ConfigurationTransformation.NodePath nodePath,
                                        ConfigurationNode configurationNode) {
                                    Object value = configurationNode.getValue();
                                    configurationNode.setValue(null);
                                    configurationNode.getAppendedNode().setValue(value);
                                    return null;
                                }
                            }).build(),
                            tBuilder().addAction(new Object[] { WILDCARD_OBJECT, WILDCARD_OBJECT, 0, "worlds" },
                                    new TransformAction() {
                                        @Override
                                        public Object[] visitPath(ConfigurationTransformation.NodePath nodePath,
                                                ConfigurationNode configurationNode) {
                                            ConfigurationNode entityNode = configurationNode.getParent()
                                                    .getParent();
                                            for (Map.Entry<Object, ? extends ConfigurationNode> ent : configurationNode
                                                    .getChildrenMap().entrySet()) {
                                                entityNode.getAppendedNode().setValue(ent.getValue())
                                                        .getNode(FileOptionSubjectData.KEY_CONTEXTS, "world")
                                                        .setValue(ent.getKey());

                                            }
                                            configurationNode.setValue(null);
                                            return null;
                                        }
                                    }).build(),
                            tBuilder().addAction(new Object[] { WILDCARD_OBJECT, WILDCARD_OBJECT, WILDCARD_OBJECT,
                                    "permissions" }, new TransformAction() {
                                        @Override
                                        public Object[] visitPath(ConfigurationTransformation.NodePath nodePath,
                                                ConfigurationNode configurationNode) {
                                            List<String> existing = configurationNode
                                                    .getList(Functions.toStringFunction());
                                            for (String permission : existing) {
                                                int value = permission.startsWith("-") ? -1 : 1;
                                                if (value < 0) {
                                                    permission = permission.substring(1);
                                                }
                                                if (permission.equals("*")) {
                                                    configurationNode.getParent().getNode("permissions-default")
                                                            .setValue(value);
                                                    continue;
                                                }
                                                permission = ConversionUtils.convertLegacyPermission(permission);
                                                if (permission.contains("*")) {
                                                    getManager().getLogger().warn(
                                                            "The permission at {} contains a now-illegal character '*'",
                                                            Arrays.toString(configurationNode.getPath()));
                                                }
                                                configurationNode.getNode(permission).setValue(value);
                                            }
                                            return null;
                                        }
                                    })
                                    .addAction(new Object[] { "users", WILDCARD_OBJECT, WILDCARD_OBJECT, "group" },
                                            new TransformAction() {
                                                @Override
                                                public Object[] visitPath(
                                                        ConfigurationTransformation.NodePath nodePath,
                                                        ConfigurationNode configurationNode) {
                                                    Object[] retPath = nodePath.getArray();
                                                    retPath[retPath.length - 1] = "parents";
                                                    for (ConfigurationNode child : configurationNode
                                                            .getChildrenList()) {
                                                        child.setValue("group:" + child.getValue());
                                                    }
                                                    return retPath;
                                                }
                                            })
                                    .addAction(new Object[] { "groups", WILDCARD_OBJECT, WILDCARD_OBJECT,
                                            "inheritance" }, new TransformAction() {
                                                @Override
                                                public Object[] visitPath(
                                                        ConfigurationTransformation.NodePath nodePath,
                                                        ConfigurationNode configurationNode) {
                                                    Object[] retPath = nodePath.getArray();
                                                    retPath[retPath.length - 1] = "parents";
                                                    for (ConfigurationNode child : configurationNode
                                                            .getChildrenList()) {
                                                        child.setValue("group:" + child.getValue());
                                                    }
                                                    return retPath;
                                                }
                                            })
                                    .addAction(new Object[] { "groups", WILDCARD_OBJECT, WILDCARD_OBJECT },
                                            new TransformAction() {
                                                @Override
                                                public Object[] visitPath(
                                                        ConfigurationTransformation.NodePath inputPath,
                                                        ConfigurationNode valueAtPath) {
                                                    ConfigurationNode defaultNode = valueAtPath.getNode("options",
                                                            "default");
                                                    if (!defaultNode.isVirtual()) {
                                                        if (defaultNode.getBoolean()) {
                                                            ConfigurationNode addToNode = null;
                                                            final ConfigurationNode defaultsParent = valueAtPath
                                                                    .getParent().getParent().getParent()
                                                                    .getNode("systems", "default");
                                                            for (ConfigurationNode node : defaultsParent
                                                                    .getChildrenList()) {
                                                                if (Objects.equal(
                                                                        node.getNode(
                                                                                FileOptionSubjectData.KEY_CONTEXTS)
                                                                                .getValue(),
                                                                        valueAtPath.getNode(
                                                                                FileOptionSubjectData.KEY_CONTEXTS)
                                                                                .getValue())) {
                                                                    addToNode = node;
                                                                    break;
                                                                }
                                                            }
                                                            if (addToNode == null) {
                                                                addToNode = defaultsParent.getAppendedNode();
                                                            }
                                                            addToNode.getNode("parents").getAppendedNode().setValue(
                                                                    "group:" + valueAtPath.getParent().getKey());
                                                        }
                                                        defaultNode.setValue(null);
                                                    }
                                                    return null;
                                                }
                                            })
                                    .build()))
                    .addVersion(1, ConfigurationTransformation.builder()
                            .addAction(new Object[] { WILDCARD_OBJECT, WILDCARD_OBJECT },
                                    movePrefixSuffixDefaultAction)
                            .addAction(new Object[] { WILDCARD_OBJECT, WILDCARD_OBJECT, "worlds", WILDCARD_OBJECT },
                                    movePrefixSuffixDefaultAction)
                            .build())
                    .build();
            int startVersion = permissionsConfig.getNode("schema-version").getInt(-1);
            versionUpdater.apply(permissionsConfig);
            int endVersion = permissionsConfig.getNode("schema-version").getInt();
            if (endVersion > startVersion) {
                getManager().getLogger().info(
                        _("%s schema version updated from %s to %s", permissionsFile, startVersion, endVersion)
                                .translateFormatted(Locale.getDefault()));
                try {
                    save().get();
                } catch (InterruptedException | ExecutionException e) {
                    throw new PermissionsLoadingException(_("While performing version upgrade"), e);
                }
            }
        }
    }

    @Override
    public void close() {

    }

    private ListenableFuture<Void> save() {
        if (saveSuppressed.get() <= 0) {
            final ListenableFutureTask<Void> ret = ListenableFutureTask.create(new Callable<Void>() {
                @Override
                public Void call() throws Exception {
                    saveSync();
                    return null;
                }
            });
            getManager().executeAsyncronously(ret);
            return ret;
        } else {
            return Futures.immediateFuture(null);
        }
    }

    private void saveSync() throws IOException {
        if (saveSuppressed.get() <= 0) {
            permissionsFileLoader.save(permissionsConfig);
        }
    }

    private String typeToSection(String type) {
        return type + "s";
    }

    @Override
    public ImmutableOptionSubjectData getDataInternal(String type, String identifier)
            throws PermissionsLoadingException {
        try {
            return FileOptionSubjectData.fromNode(permissionsConfig.getNode(typeToSection(type), identifier));
        } catch (ObjectMappingException e) {
            throw new PermissionsLoadingException(_("While deserializing subject data for %s:", identifier), e);
        }
    }

    @Override
    protected ListenableFuture<ImmutableOptionSubjectData> setDataInternal(String type, String identifier,
            final ImmutableOptionSubjectData data) {
        try {
            if (data == null) {
                permissionsConfig.getNode(typeToSection(type), identifier).setValue(null);
                return Futures.transform(save(), Functions.<ImmutableOptionSubjectData>constant(null));
            }

            final FileOptionSubjectData fileData;

            if (data instanceof FileOptionSubjectData) {
                fileData = (FileOptionSubjectData) data;
            } else {
                fileData = new FileOptionSubjectData();
                ConversionUtils.transfer(data, fileData);
            }
            fileData.serialize(permissionsConfig.getNode(typeToSection(type), identifier));
            return Futures.transform(save(), new Function<Void, ImmutableOptionSubjectData>() {
                @Nullable
                @Override
                public ImmutableOptionSubjectData apply(Void input) {
                    return fileData;
                }
            });
        } catch (ObjectMappingException e) {
            return Futures.immediateFailedFuture(e);
        }
    }

    @Override
    public boolean isRegistered(String type, String identifier) {
        return !permissionsConfig.getNode(typeToSection(type), identifier).isVirtual();
    }

    @Override
    @SuppressWarnings("unchecked")
    public Iterable<String> getAllIdentifiers(String type) {
        return (Set) this.permissionsConfig.getNode(typeToSection(type)).getChildrenMap().keySet();
    }

    @Override
    public Iterable<String> getRegisteredTypes() {
        return Iterables.transform(
                Maps.filterValues(this.permissionsConfig.getChildrenMap(), new Predicate<ConfigurationNode>() {
                    @Override
                    public boolean apply(@Nullable ConfigurationNode input) {
                        return input != null && input.hasMapChildren();
                    }
                }).keySet(), new Function<Object, String>() {
                    @Nullable
                    @Override
                    public String apply(@Nullable Object input) {
                        final String typeStr = input.toString();
                        return typeStr.substring(0, typeStr.length() - 1); // trim trailing s
                    }
                });
    }

    @Override
    public Iterable<Map.Entry<Map.Entry<String, String>, ImmutableOptionSubjectData>> getAll() {
        return Iterables.concat(Iterables.transform(permissionsConfig.getChildrenMap().keySet(),
                new Function<Object, Iterable<Map.Entry<Map.Entry<String, String>, ImmutableOptionSubjectData>>>() {
                    @Nullable
                    @Override
                    public Iterable<Map.Entry<Map.Entry<String, String>, ImmutableOptionSubjectData>> apply(
                            @Nullable final Object type) {
                        final String typeStr = type.toString();

                        return Iterables.transform(getAll(typeStr.substring(0, typeStr.length() - 1)),
                                new Function<Map.Entry<String, ImmutableOptionSubjectData>, Map.Entry<Map.Entry<String, String>, ImmutableOptionSubjectData>>() {
                                    @Nullable
                                    @Override
                                    public Map.Entry<Map.Entry<String, String>, ImmutableOptionSubjectData> apply(
                                            Map.Entry<String, ImmutableOptionSubjectData> input2) {
                                        return Maps.immutableEntry(
                                                Maps.immutableEntry(type.toString(), input2.getKey()),
                                                input2.getValue());
                                    }
                                });
                    }
                }));
    }

    @Override
    protected <T> T performBulkOperationSync(Function<DataStore, T> function) throws Exception {
        saveSuppressed.getAndIncrement();
        T ret;
        try {
            ret = function.apply(this);
        } finally {
            saveSuppressed.getAndDecrement();
        }
        saveSync();
        return ret;
    }
}