/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.nifi.registry.security.authorization.file;

import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.registry.security.authorization.AuthorizerConfigurationContext;
import org.apache.nifi.registry.security.authorization.ConfigurableUserGroupProvider;
import org.apache.nifi.registry.security.authorization.Group;
import org.apache.nifi.registry.security.authorization.User;
import org.apache.nifi.registry.security.authorization.UserAndGroups;
import org.apache.nifi.registry.security.authorization.UserGroupProviderInitializationContext;
import org.apache.nifi.registry.security.authorization.annotation.AuthorizerContext;
import org.apache.nifi.registry.security.authorization.exception.AuthorizationAccessException;
import org.apache.nifi.registry.security.authorization.exception.UninheritableAuthorizationsException;
import org.apache.nifi.registry.security.authorization.file.tenants.generated.Groups;
import org.apache.nifi.registry.security.authorization.file.tenants.generated.Tenants;
import org.apache.nifi.registry.security.authorization.file.tenants.generated.Users;
import org.apache.nifi.registry.security.authorization.util.UserGroupProviderUtils;
import org.apache.nifi.registry.security.exception.SecurityProviderCreationException;
import org.apache.nifi.registry.security.exception.SecurityProviderDestructionException;
import org.apache.nifi.registry.security.identity.IdentityMapper;
import org.apache.nifi.registry.util.PropertyValue;
import org.apache.nifi.xml.processing.ProcessingException;
import org.apache.nifi.xml.processing.parsers.DocumentProvider;
import org.apache.nifi.xml.processing.parsers.StandardDocumentProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

public class FileUserGroupProvider implements ConfigurableUserGroupProvider {

    private static final Logger logger = LoggerFactory.getLogger(FileUserGroupProvider.class);

    private static final String TENANTS_XSD = "/tenants.xsd";
    private static final String JAXB_TENANTS_PATH = "org.apache.nifi.registry.security.authorization.file.tenants.generated";

    private static final JAXBContext JAXB_TENANTS_CONTEXT = initializeJaxbContext(JAXB_TENANTS_PATH);

    /**
     * Load the JAXBContext.
     */
    private static JAXBContext initializeJaxbContext(final String contextPath) {
        try {
            return JAXBContext.newInstance(contextPath, FileAuthorizer.class.getClassLoader());
            //return JAXBContext.newInstance(contextPath);
        } catch (JAXBException e) {
            throw new RuntimeException("Unable to create JAXBContext: " + e);
        }
    }

    private static final XMLOutputFactory XML_OUTPUT_FACTORY = XMLOutputFactory.newInstance();

    private static final String USER_ELEMENT = "user";
    private static final String GROUP_USER_ELEMENT = "groupUser";
    private static final String GROUP_ELEMENT = "group";
    private static final String IDENTIFIER_ATTR = "identifier";
    private static final String IDENTITY_ATTR = "identity";
    private static final String NAME_ATTR = "name";

    static final String PROP_TENANTS_FILE = "Users File";

    private Schema tenantsSchema;
    private File tenantsFile;
    private Set<String> initialUserIdentities;
    private IdentityMapper identityMapper;

    private final AtomicReference<UserGroupHolder> userGroupHolder = new AtomicReference<>();

    @Override
    public void initialize(UserGroupProviderInitializationContext initializationContext) throws SecurityProviderCreationException {
        try {
            final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
            tenantsSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(TENANTS_XSD));
        } catch (Exception e) {
            throw new SecurityProviderCreationException(e);
        }
    }

    @Override
    public void onConfigured(AuthorizerConfigurationContext configurationContext) throws SecurityProviderCreationException {
        try {
            final PropertyValue tenantsPath = configurationContext.getProperty(PROP_TENANTS_FILE);
            if (StringUtils.isBlank(tenantsPath.getValue())) {
                throw new SecurityProviderCreationException("The users file must be specified.");
            }

            // get the tenants file and ensure it exists
            tenantsFile = new File(tenantsPath.getValue());
            if (!tenantsFile.exists()) {
                logger.info("Creating new users file at {}", new Object[] {tenantsFile.getAbsolutePath()});
                saveTenants(new Tenants());
            }

            // extract any nifi identities
            initialUserIdentities = UserGroupProviderUtils.getInitialUserIdentities(configurationContext, identityMapper);

            load();

            logger.info(String.format("Users/Groups file loaded at %s", new Date().toString()));
        } catch (SecurityProviderCreationException | JAXBException | IllegalStateException | SAXException e) {
            throw new SecurityProviderCreationException(e);
        }
    }

    @Override
    public Set<User> getUsers() throws AuthorizationAccessException {
        return userGroupHolder.get().getAllUsers();
    }

    @Override
    public synchronized User addUser(User user) throws AuthorizationAccessException {
        if (user == null) {
            throw new IllegalArgumentException("User cannot be null");
        }

        final org.apache.nifi.registry.security.authorization.file.tenants.generated.User jaxbUser = createJAXBUser(user);

        final UserGroupHolder holder = userGroupHolder.get();
        final Tenants tenants = holder.getTenants();
        tenants.getUsers().getUser().add(jaxbUser);

        saveAndRefreshHolder(tenants);

        return userGroupHolder.get().getUsersById().get(user.getIdentifier());
    }

    @Override
    public User getUser(String identifier) throws AuthorizationAccessException {
        if (identifier == null) {
            return null;
        }

        final UserGroupHolder holder = userGroupHolder.get();
        return holder.getUsersById().get(identifier);
    }

    @Override
    public synchronized User updateUser(User user) throws AuthorizationAccessException {
        if (user == null) {
            throw new IllegalArgumentException("User cannot be null");
        }

        final UserGroupHolder holder = userGroupHolder.get();
        final Tenants tenants = holder.getTenants();

        final List<org.apache.nifi.registry.security.authorization.file.tenants.generated.User> users = tenants.getUsers().getUser();

        // fine the User that needs to be updated
        org.apache.nifi.registry.security.authorization.file.tenants.generated.User updateUser = null;
        for (org.apache.nifi.registry.security.authorization.file.tenants.generated.User jaxbUser : users) {
            if (user.getIdentifier().equals(jaxbUser.getIdentifier())) {
                updateUser = jaxbUser;
                break;
            }
        }

        // if user wasn't found return null, otherwise update the user and save changes
        if (updateUser == null) {
            return null;
        } else {
            updateUser.setIdentity(user.getIdentity());
            saveAndRefreshHolder(tenants);

            return userGroupHolder.get().getUsersById().get(user.getIdentifier());
        }
    }

    @Override
    public User getUserByIdentity(String identity) throws AuthorizationAccessException {
        if (identity == null) {
            return null;
        }

        final UserGroupHolder holder = userGroupHolder.get();
        return holder.getUsersByIdentity().get(identity);
    }

    @Override
    public synchronized User deleteUser(User user) throws AuthorizationAccessException {
        if (user == null) {
            throw new IllegalArgumentException("User cannot be null");
        }

        return deleteUser(user.getIdentifier());
    }

    private synchronized User deleteUser(String userIdentifier) throws AuthorizationAccessException {
        if (userIdentifier == null) {
            throw new IllegalArgumentException("User identifier cannot be null");
        }

        final UserGroupHolder holder = userGroupHolder.get();
        final User deletedUser = holder.getUsersById().get(userIdentifier);
        if (deletedUser == null) {
            return null;
        }

        // for each group iterate over the user references and remove the user reference if it matches the user being deleted
        final Tenants tenants = holder.getTenants();
        for (org.apache.nifi.registry.security.authorization.file.tenants.generated.Group group : tenants.getGroups().getGroup()) {
            Iterator<org.apache.nifi.registry.security.authorization.file.tenants.generated.Group.User> groupUserIter = group.getUser().iterator();
            while (groupUserIter.hasNext()) {
                org.apache.nifi.registry.security.authorization.file.tenants.generated.Group.User groupUser = groupUserIter.next();
                if (groupUser.getIdentifier().equals(userIdentifier)) {
                    groupUserIter.remove();
                    break;
                }
            }
        }

        // remove the actual user
        Iterator<org.apache.nifi.registry.security.authorization.file.tenants.generated.User> iter = tenants.getUsers().getUser().iterator();
        while (iter.hasNext()) {
            org.apache.nifi.registry.security.authorization.file.tenants.generated.User jaxbUser = iter.next();
            if (userIdentifier.equals(jaxbUser.getIdentifier())) {
                iter.remove();
                break;
            }
        }

        saveAndRefreshHolder(tenants);
        return deletedUser;
    }

    @Override
    public Set<Group> getGroups() throws AuthorizationAccessException {
        return userGroupHolder.get().getAllGroups();
    }

    @Override
    public synchronized Group addGroup(Group group) throws AuthorizationAccessException {
        if (group == null) {
            throw new IllegalArgumentException("Group cannot be null");
        }

        final UserGroupHolder holder = userGroupHolder.get();
        final Tenants tenants = holder.getTenants();

        // create a new JAXB Group based on the incoming Group
        final org.apache.nifi.registry.security.authorization.file.tenants.generated.Group jaxbGroup =
                new org.apache.nifi.registry.security.authorization.file.tenants.generated.Group();
        jaxbGroup.setIdentifier(group.getIdentifier());
        jaxbGroup.setName(group.getName());

        // add each user to the group
        for (String groupUser : group.getUsers()) {
            org.apache.nifi.registry.security.authorization.file.tenants.generated.Group.User jaxbGroupUser =
                    new org.apache.nifi.registry.security.authorization.file.tenants.generated.Group.User();
            jaxbGroupUser.setIdentifier(groupUser);
            jaxbGroup.getUser().add(jaxbGroupUser);
        }

        tenants.getGroups().getGroup().add(jaxbGroup);
        saveAndRefreshHolder(tenants);

        return userGroupHolder.get().getGroupsById().get(group.getIdentifier());
    }

    @Override
    public Group getGroup(String identifier) throws AuthorizationAccessException {
        if (identifier == null) {
            return null;
        }
        return userGroupHolder.get().getGroupsById().get(identifier);
    }

    @Override
    public UserAndGroups getUserAndGroups(final String identity) throws AuthorizationAccessException {
        final UserGroupHolder holder = userGroupHolder.get();
        final User user = holder.getUser(identity);
        final Set<Group> groups = holder.getGroups(identity);

        return new UserAndGroups() {
            @Override
            public User getUser() {
                return user;
            }

            @Override
            public Set<Group> getGroups() {
                return groups;
            }
        };
    }

    @Override
    public synchronized Group updateGroup(Group group) throws AuthorizationAccessException {
        if (group == null) {
            throw new IllegalArgumentException("Group cannot be null");
        }

        final UserGroupHolder holder = userGroupHolder.get();
        final Tenants tenants = holder.getTenants();

        // find the group that needs to be update
        org.apache.nifi.registry.security.authorization.file.tenants.generated.Group updateGroup = null;
        for (org.apache.nifi.registry.security.authorization.file.tenants.generated.Group jaxbGroup : tenants.getGroups().getGroup()) {
            if (jaxbGroup.getIdentifier().equals(group.getIdentifier())) {
                updateGroup = jaxbGroup;
                break;
            }
        }

        // if the group wasn't found return null, otherwise update the group and save changes
        if (updateGroup == null) {
            return null;
        }

        // reset the list of users and add each user to the group
        updateGroup.getUser().clear();
        for (String groupUser : group.getUsers()) {
            org.apache.nifi.registry.security.authorization.file.tenants.generated.Group.User jaxbGroupUser =
                    new org.apache.nifi.registry.security.authorization.file.tenants.generated.Group.User();
            jaxbGroupUser.setIdentifier(groupUser);
            updateGroup.getUser().add(jaxbGroupUser);
        }

        updateGroup.setName(group.getName());
        saveAndRefreshHolder(tenants);

        return userGroupHolder.get().getGroupsById().get(group.getIdentifier());
    }

    @Override
    public synchronized Group deleteGroup(Group group) throws AuthorizationAccessException {
        if (group == null) {
            throw new IllegalArgumentException("Group cannot be null");
        }

        return deleteGroup(group.getIdentifier());
    }

    private synchronized Group deleteGroup(String groupIdentifier) throws AuthorizationAccessException {
        if (groupIdentifier == null) {
            throw new IllegalArgumentException("Group identifier cannot be null");
        }

        final UserGroupHolder holder = userGroupHolder.get();
        final Group deletedGroup = holder.getGroupsById().get(groupIdentifier);
        if (deletedGroup == null) {
            return null;
        }

        // now remove the actual group from the top-level list of groups
        final Tenants tenants = holder.getTenants();
        Iterator<org.apache.nifi.registry.security.authorization.file.tenants.generated.Group> iter = tenants.getGroups().getGroup().iterator();
        while (iter.hasNext()) {
            org.apache.nifi.registry.security.authorization.file.tenants.generated.Group jaxbGroup = iter.next();
            if (groupIdentifier.equals(jaxbGroup.getIdentifier())) {
                iter.remove();
                break;
            }
        }

        saveAndRefreshHolder(tenants);
        return deletedGroup;
    }

    UserGroupHolder getUserGroupHolder() {
        return userGroupHolder.get();
    }

    @AuthorizerContext
    public void setIdentityMapper(final IdentityMapper identityMapper) {
        this.identityMapper = identityMapper;
    }

    @Override
    public synchronized void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
        final UsersAndGroups usersAndGroups = parseUsersAndGroups(fingerprint);
        usersAndGroups.getUsers().forEach(user -> addUser(user));
        usersAndGroups.getGroups().forEach(group -> addGroup(group));
    }

    @Override
    public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException {
        try {
            // ensure we understand the proposed fingerprint
            parseUsersAndGroups(proposedFingerprint);
        } catch (final AuthorizationAccessException e) {
            throw new UninheritableAuthorizationsException("Unable to parse the proposed fingerprint: " + e);
        }

        final UserGroupHolder usersAndGroups = userGroupHolder.get();

        // ensure we are in a proper state to inherit the fingerprint
        if (!usersAndGroups.getAllUsers().isEmpty() || !usersAndGroups.getAllGroups().isEmpty()) {
            throw new UninheritableAuthorizationsException("Proposed fingerprint is not inheritable because the current users and groups is not empty.");
        }
    }

    @Override
    public String getFingerprint() throws AuthorizationAccessException {
        final UserGroupHolder usersAndGroups = userGroupHolder.get();

        final List<User> users = new ArrayList<>(usersAndGroups.getAllUsers());
        Collections.sort(users, Comparator.comparing(User::getIdentifier));

        final List<Group> groups = new ArrayList<>(usersAndGroups.getAllGroups());
        Collections.sort(groups, Comparator.comparing(Group::getIdentifier));

        XMLStreamWriter writer = null;
        final StringWriter out = new StringWriter();
        try {
            writer = XML_OUTPUT_FACTORY.createXMLStreamWriter(out);
            writer.writeStartDocument();
            writer.writeStartElement("tenants");

            for (User user : users) {
                writeUser(writer, user);
            }
            for (Group group : groups) {
                writeGroup(writer, group);
            }

            writer.writeEndElement();
            writer.writeEndDocument();
            writer.flush();
        } catch (XMLStreamException e) {
            throw new AuthorizationAccessException("Unable to generate fingerprint", e);
        } finally {
            if (writer != null) {
                try {
                    writer.close();
                } catch (XMLStreamException e) {
                    // nothing to do here
                }
            }
        }

        return out.toString();
    }

    private UsersAndGroups parseUsersAndGroups(final String fingerprint) {
        final List<User> users = new ArrayList<>();
        final List<Group> groups = new ArrayList<>();

        final byte[] fingerprintBytes = fingerprint.getBytes(StandardCharsets.UTF_8);
        try (final ByteArrayInputStream in = new ByteArrayInputStream(fingerprintBytes)) {
            final DocumentProvider documentProvider = new StandardDocumentProvider();
            final Document document = documentProvider.parse(in);
            final Element rootElement = document.getDocumentElement();

            // parse all the users and add them to the current user group provider
            NodeList userNodes = rootElement.getElementsByTagName(USER_ELEMENT);
            for (int i=0; i < userNodes.getLength(); i++) {
                Node userNode = userNodes.item(i);
                users.add(parseUser((Element) userNode));
            }

            // parse all the groups and add them to the current user group provider
            NodeList groupNodes = rootElement.getElementsByTagName(GROUP_ELEMENT);
            for (int i=0; i < groupNodes.getLength(); i++) {
                Node groupNode = groupNodes.item(i);
                groups.add(parseGroup((Element) groupNode));
            }
        } catch (final ProcessingException | IOException e) {
            throw new AuthorizationAccessException("Unable to parse fingerprint", e);
        }

        return new UsersAndGroups(users, groups);
    }

    private User parseUser(final Element element) {
        final User.Builder builder = new User.Builder()
                .identifier(element.getAttribute(IDENTIFIER_ATTR))
                .identity(element.getAttribute(IDENTITY_ATTR));

        return builder.build();
    }

    private Group parseGroup(final Element element) {
        final Group.Builder builder = new Group.Builder()
                .identifier(element.getAttribute(IDENTIFIER_ATTR))
                .name(element.getAttribute(NAME_ATTR));

        NodeList groupUsers = element.getElementsByTagName(GROUP_USER_ELEMENT);
        for (int i=0; i < groupUsers.getLength(); i++) {
            Element groupUserNode = (Element) groupUsers.item(i);
            builder.addUser(groupUserNode.getAttribute(IDENTIFIER_ATTR));
        }

        return builder.build();
    }

    private void writeUser(final XMLStreamWriter writer, final User user) throws XMLStreamException {
        writer.writeStartElement(USER_ELEMENT);
        writer.writeAttribute(IDENTIFIER_ATTR, user.getIdentifier());
        writer.writeAttribute(IDENTITY_ATTR, user.getIdentity());
        writer.writeEndElement();
    }

    private void writeGroup(final XMLStreamWriter writer, final Group group) throws XMLStreamException {
        List<String> users = new ArrayList<>(group.getUsers());
        Collections.sort(users);

        writer.writeStartElement(GROUP_ELEMENT);
        writer.writeAttribute(IDENTIFIER_ATTR, group.getIdentifier());
        writer.writeAttribute(NAME_ATTR, group.getName());

        for (String user : users) {
            writer.writeStartElement(GROUP_USER_ELEMENT);
            writer.writeAttribute(IDENTIFIER_ATTR, user);
            writer.writeEndElement();
        }

        writer.writeEndElement();
    }

    private org.apache.nifi.registry.security.authorization.file.tenants.generated.User createJAXBUser(User user) {
        final org.apache.nifi.registry.security.authorization.file.tenants.generated.User jaxbUser =
                new org.apache.nifi.registry.security.authorization.file.tenants.generated.User();
        jaxbUser.setIdentifier(user.getIdentifier());
        jaxbUser.setIdentity(user.getIdentity());
        return jaxbUser;
    }

    /**
     * Loads the authorizations file and populates the AuthorizationsHolder, only called during start-up.
     *
     * @throws JAXBException            Unable to reload the authorized users file
     * @throws IllegalStateException    Unable to sync file with restore
     * @throws SAXException             Unable to unmarshall tenants
     */
    private synchronized void load() throws JAXBException, IllegalStateException, SAXException {
        final Tenants tenants = unmarshallTenants();
        if (tenants.getUsers() == null) {
            tenants.setUsers(new Users());
        }
        if (tenants.getGroups() == null) {
            tenants.setGroups(new Groups());
        }

        final UserGroupHolder userGroupHolder = new UserGroupHolder(tenants);
        final boolean emptyTenants = userGroupHolder.getAllUsers().isEmpty() && userGroupHolder.getAllGroups().isEmpty();

        if (emptyTenants) {

            populateInitialUsers(tenants);

            // save any changes that were made and repopulate the holder
            saveAndRefreshHolder(tenants);
        } else {
            this.userGroupHolder.set(userGroupHolder);
        }
    }

    private void saveTenants(final Tenants tenants) throws JAXBException {
        final Marshaller marshaller = JAXB_TENANTS_CONTEXT.createMarshaller();
        marshaller.setSchema(tenantsSchema);
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(tenants, tenantsFile);
    }

    private Tenants unmarshallTenants() throws JAXBException {
        final Unmarshaller unmarshaller = JAXB_TENANTS_CONTEXT.createUnmarshaller();
        unmarshaller.setSchema(tenantsSchema);

        final JAXBElement<Tenants> element = unmarshaller.unmarshal(new StreamSource(tenantsFile), Tenants.class);
        return element.getValue();
    }

    private void populateInitialUsers(final Tenants tenants) {
        for (String initialUserIdentity : initialUserIdentities) {
            getOrCreateUser(tenants, initialUserIdentity);
        }
    }

    /**
     * Finds the User with the given identity, or creates a new one and adds it to the Tenants.
     *
     * @param tenants the Tenants reference
     * @param userIdentity the user identity to find or create
     * @return the User from Tenants with the given identity, or a new instance that was added to Tenants
     */
    private org.apache.nifi.registry.security.authorization.file.tenants.generated.User getOrCreateUser(final Tenants tenants, final String userIdentity) {
        if (StringUtils.isBlank(userIdentity)) {
            return null;
        }

        org.apache.nifi.registry.security.authorization.file.tenants.generated.User foundUser = null;
        for (org.apache.nifi.registry.security.authorization.file.tenants.generated.User user : tenants.getUsers().getUser()) {
            if (user.getIdentity().equals(userIdentity)) {
                foundUser = user;
                break;
            }
        }

        if (foundUser == null) {
            final User newUser = new User.Builder().identifierGenerateFromSeed(userIdentity).identity(userIdentity).build();
            foundUser = new org.apache.nifi.registry.security.authorization.file.tenants.generated.User();
            foundUser.setIdentifier(newUser.getIdentifier());
            foundUser.setIdentity(newUser.getIdentity());
            tenants.getUsers().getUser().add(foundUser);
        }

        return foundUser;
    }

    /**
     * Finds the Group with the given name, or creates a new one and adds it to Tenants.
     *
     * @param tenants the Tenants reference
     * @param groupName the name of the group to look for
     * @return the Group from Tenants with the given name, or a new instance that was added to Tenants
     */
    private org.apache.nifi.registry.security.authorization.file.tenants.generated.Group getOrCreateGroup(final Tenants tenants, final String groupName) {
        if (StringUtils.isBlank(groupName)) {
            return null;
        }

        org.apache.nifi.registry.security.authorization.file.tenants.generated.Group foundGroup = null;
        for (org.apache.nifi.registry.security.authorization.file.tenants.generated.Group group : tenants.getGroups().getGroup()) {
            if (group.getName().equals(groupName)) {
                foundGroup = group;
                break;
            }
        }

        if (foundGroup == null) {
            final Group newGroup = new Group.Builder().identifierGenerateFromSeed(groupName).name(groupName).build();
            foundGroup = new org.apache.nifi.registry.security.authorization.file.tenants.generated.Group();
            foundGroup.setIdentifier(newGroup.getIdentifier());
            foundGroup.setName(newGroup.getName());
            tenants.getGroups().getGroup().add(foundGroup);
        }

        return foundGroup;
    }

    /**
     * Saves the Authorizations instance by marshalling to a file, then re-populates the
     * in-memory data structures and sets the new holder.
     *
     * Synchronized to ensure only one thread writes the file at a time.
     *
     * @param tenants the tenants to save and populate from
     * @throws AuthorizationAccessException if an error occurs saving the authorizations
     */
    private synchronized void saveAndRefreshHolder(final Tenants tenants) throws AuthorizationAccessException {
        try {
            saveTenants(tenants);

            this.userGroupHolder.set(new UserGroupHolder(tenants));
        } catch (JAXBException e) {
            throw new AuthorizationAccessException("Unable to save Authorizations", e);
        }
    }

    @Override
    public void preDestruction() throws SecurityProviderDestructionException {
    }

    private static class UsersAndGroups {
        final List<User> users;
        final List<Group> groups;

        public UsersAndGroups(List<User> users, List<Group> groups) {
            this.users = users;
            this.groups = groups;
        }

        public List<User> getUsers() {
            return users;
        }

        public List<Group> getGroups() {
            return groups;
        }
    }
}
