fr.gael.dhus.sync.impl.ODataUserSynchronizer.java Source code

Java tutorial

Introduction

Here is the source code for fr.gael.dhus.sync.impl.ODataUserSynchronizer.java

Source

/*
 * Data Hub Service (DHuS) - For Space data distribution.
 * Copyright (C) 2016 GAEL Systems
 *
 * This file is part of DHuS software sources.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package fr.gael.dhus.sync.impl;

import fr.gael.dhus.database.object.Role;
import fr.gael.dhus.database.object.SynchronizerConf;
import fr.gael.dhus.database.object.User;
import fr.gael.dhus.database.object.User.PasswordEncryption;
import fr.gael.dhus.database.object.restriction.LockedAccessRestriction;
import fr.gael.dhus.olingo.ODataClient;
import fr.gael.dhus.olingo.v1.entityset.SystemRoleEntitySet;
import fr.gael.dhus.olingo.v1.entityset.UserEntitySet;
import fr.gael.dhus.service.ISynchronizerService;
import fr.gael.dhus.service.UserService;
import fr.gael.dhus.service.exception.RequiredFieldMissingException;
import fr.gael.dhus.service.exception.RootNotModifiableException;
import fr.gael.dhus.spring.context.ApplicationContextProvider;
import fr.gael.dhus.sync.Synchronizer;

import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections.SetUtils;
import org.apache.log4j.Level;

import org.apache.log4j.Logger;

import org.apache.olingo.odata2.api.ep.entry.ODataEntry;
import org.apache.olingo.odata2.api.ep.feed.ODataFeed;
import org.apache.olingo.odata2.api.exception.ODataException;

import org.hibernate.exception.LockAcquisitionException;

import org.springframework.dao.CannotAcquireLockException;
import org.springframework.web.util.UriUtils;

/**
 * Synchronizes users through the OData user API.
 */
public class ODataUserSynchronizer extends Synchronizer {
    /** Log. */
    private static final Logger LOGGER = Logger.getLogger(ODataUserSynchronizer.class);

    /** Synchronizer Service, to save the  */
    private static final ISynchronizerService SYNC_SERVICE = ApplicationContextProvider
            .getBean(ISynchronizerService.class);

    /** User Service, to create user objects.  */
    private static final UserService USER_SERVICE = ApplicationContextProvider.getBean(UserService.class);

    /** An {@link ODataClient} configured to query another DHuS OData service. */
    private final ODataClient client;

    /** Credentials: username. */
    private final String serviceUser;

    /** Credentials: password. */
    private final String servicePass;

    /** Current offset in remote's user list ($skip parameter) */
    private int skip;

    /** Size of a Page (number of users to retrieve at once, $top parameter). */
    private int pageSize;

    /** Force user synchronizer, without checking creation date. */
    private boolean force;

    /**
     * Creates a new UserSynchronizer.
     *
     * @param sc configuration for this synchronizer.
     *
     * @throws java.io.IOException
     * @throws org.apache.olingo.odata2.api.exception.ODataException
     */
    public ODataUserSynchronizer(SynchronizerConf sc) throws IOException, ODataException {
        super(sc);
        // Checks if required configuration is set
        String urilit = sc.getConfig("service_uri");
        serviceUser = sc.getConfig("service_username");
        servicePass = sc.getConfig("service_password");

        if (urilit == null || urilit.isEmpty()) {
            throw new IllegalStateException("`service_uri` is not set");
        }

        try {
            client = new ODataClient(urilit, serviceUser, servicePass);
        } catch (URISyntaxException e) {
            throw new IllegalStateException("`service_uri` is malformed");
        }

        String skip = sc.getConfig("skip");
        if (skip != null && !skip.isEmpty()) {
            this.skip = Integer.parseInt(skip);
        } else {
            this.skip = 0;
        }

        String page_size = sc.getConfig("page_size");
        if (page_size != null && !page_size.isEmpty()) {
            pageSize = Integer.decode(page_size);
        } else {
            pageSize = 500;
        }

        String cfgForce = sc.getConfig("force");
        if (cfgForce != null && !cfgForce.isEmpty()) {
            force = Boolean.parseBoolean(cfgForce);
        } else {
            force = false;
        }
    }

    /** Prints log line prefixed with the sync ID. */
    private void log(Level level, String message) {
        LOGGER.log(level, "UserSync#" + getId() + ' ' + message);
    }

    /** Prints log line prefixed with the sync ID. */
    private void log(Level level, String message, Throwable ex) {
        LOGGER.log(level, "UserSync#" + getId() + ' ' + message, ex);
    }

    /** Logs how much time an OData command consumed. */
    private ODataFeed readFeedLogPerf(String query, Map<String, String> params) throws IOException, ODataException {
        long delta_time = System.currentTimeMillis();
        ODataFeed feed = client.readFeed(query, params);
        log(Level.DEBUG, "query(" + query + ") done in " + delta_time + "ms");
        return feed;
    }

    @Override
    public boolean synchronize() throws InterruptedException {
        int created = 0, updated = 0;
        try {
            // Makes query parameters
            Map<String, String> query_param = new HashMap<>();

            if (skip != 0) {
                query_param.put("$skip", String.valueOf(skip));
            }

            query_param.put("$top", String.valueOf(pageSize));

            log(Level.DEBUG, "Querying users from " + skip + " to " + skip + pageSize);
            ODataFeed userfeed = readFeedLogPerf("/Users", query_param);

            // For each entry, creates a DataBase Object
            for (ODataEntry pdt : userfeed.getEntries()) {
                String username = null;
                try {
                    Map<String, Object> props = pdt.getProperties();

                    username = (String) props.get(UserEntitySet.USERNAME);
                    String email = (String) props.get(UserEntitySet.EMAIL);
                    String firstname = (String) props.get(UserEntitySet.FIRSTNAME);
                    String lastname = (String) props.get(UserEntitySet.LASTNAME);
                    String country = (String) props.get(UserEntitySet.COUNTRY);
                    String domain = (String) props.get(UserEntitySet.DOMAIN);
                    String subdomain = (String) props.get(UserEntitySet.SUBDOMAIN);
                    String usage = (String) props.get(UserEntitySet.USAGE);
                    String subusage = (String) props.get(UserEntitySet.SUBUSAGE);
                    String phone = (String) props.get(UserEntitySet.PHONE);
                    String address = (String) props.get(UserEntitySet.ADDRESS);
                    String hash = (String) props.get(UserEntitySet.HASH);
                    String password = (String) props.get(UserEntitySet.PASSWORD);
                    Date creation = ((GregorianCalendar) props.get(UserEntitySet.CREATED)).getTime();

                    // Uses the Scheme encoder as it is the most restrictives, it only allows
                    // '+' (forbidden char in usernames, so it's ok)
                    // '-' (forbidden char in usernames, so it's ok)
                    // '.' (allowed char in usernames, not problematic)
                    // the alphanumeric character class (a-z A-Z 0-9)
                    String encoded_username = UriUtils.encodeScheme(username, "UTF-8");

                    // Retrieves Roles
                    String roleq = String.format("/Users('%s')/SystemRoles", encoded_username);
                    ODataFeed userrole = readFeedLogPerf(roleq, null);

                    List<ODataEntry> roles = userrole.getEntries();
                    List<Role> new_roles = new ArrayList<>();
                    for (ODataEntry role : roles) {
                        String rolename = (String) role.getProperties().get(SystemRoleEntitySet.NAME);
                        new_roles.add(Role.valueOf(rolename));
                    }

                    // Has restriction?
                    String restricq = String.format("/Users('%s')/Restrictions", encoded_username);
                    ODataFeed userrestric = readFeedLogPerf(restricq, null);
                    boolean has_restriction = !userrestric.getEntries().isEmpty();

                    // Reads user in database, may be null
                    User user = USER_SERVICE.getUserNoCheck(username);

                    // Updates existing user
                    if (user != null && (force || creation.equals(user.getCreated()))) {
                        boolean changed = false;

                        // I wish users had their `Updated` field exposed on OData
                        if (!username.equals(user.getUsername())) {
                            user.setUsername(username);
                            changed = true;
                        }
                        if (email == null && user.getEmail() != null
                                || email != null && !email.equals(user.getEmail())) {
                            user.setEmail(email);
                            changed = true;
                        }
                        if (firstname == null && user.getFirstname() != null
                                || firstname != null && !firstname.equals(user.getFirstname())) {
                            user.setFirstname(firstname);
                            changed = true;
                        }
                        if (lastname == null && user.getLastname() != null
                                || lastname != null && !lastname.equals(user.getLastname())) {
                            user.setLastname(lastname);
                            changed = true;
                        }
                        if (country == null && user.getCountry() != null
                                || country != null && !country.equals(user.getCountry())) {
                            user.setCountry(country);
                            changed = true;
                        }
                        if (domain == null && user.getDomain() != null
                                || domain != null && !domain.equals(user.getDomain())) {
                            user.setDomain(domain);
                            changed = true;
                        }
                        if (subdomain == null && user.getSubDomain() != null
                                || subdomain != null && !subdomain.equals(user.getSubDomain())) {
                            user.setSubDomain(subdomain);
                            changed = true;
                        }
                        if (usage == null && user.getUsage() != null
                                || usage != null && !usage.equals(user.getUsage())) {
                            user.setUsage(usage);
                            changed = true;
                        }
                        if (subusage == null && user.getSubUsage() != null
                                || subusage != null && !subusage.equals(user.getSubUsage())) {
                            user.setSubUsage(subusage);
                            changed = true;
                        }
                        if (phone == null && user.getPhone() != null
                                || phone != null && !phone.equals(user.getPhone())) {
                            user.setPhone(phone);
                            changed = true;
                        }
                        if (address == null && user.getAddress() != null
                                || address != null && !address.equals(user.getAddress())) {
                            user.setAddress(address);
                            changed = true;
                        }

                        if (password == null && user.getPassword() != null
                                || password != null && !password.equals(user.getPassword())) {
                            if (hash == null)
                                hash = PasswordEncryption.NONE.getAlgorithmKey();

                            user.setEncryptedPassword(password, User.PasswordEncryption.valueOf(hash));
                            changed = true;
                        }

                        //user.setPasswordEncryption(User.PasswordEncryption.valueOf(hash));

                        if (!SetUtils.isEqualSet(user.getRoles(), new_roles)) {
                            user.setRoles(new_roles);
                            changed = true;
                        }

                        if (has_restriction != !user.getRestrictions().isEmpty()) {
                            if (has_restriction) {
                                user.addRestriction(new LockedAccessRestriction());
                            } else {
                                user.setRestrictions(Collections.EMPTY_SET);
                            }
                            changed = true;
                        }

                        if (changed) {
                            log(Level.DEBUG, "Updating user " + user.getUsername());
                            USER_SERVICE.systemUpdateUser(user);
                            updated++;
                        }
                    }
                    // Creates new user
                    else if (user == null) {
                        user = new User();

                        user.setUsername(username);
                        user.setEmail(email);
                        user.setFirstname(firstname);
                        user.setLastname(lastname);
                        user.setCountry(country);
                        user.setDomain(domain);
                        user.setSubDomain(subdomain);
                        user.setUsage(usage);
                        user.setSubUsage(subusage);
                        user.setPhone(phone);
                        user.setAddress(address);
                        user.setPassword(password);
                        //user.setPasswordEncryption(User.PasswordEncryption.valueOf(hash));
                        user.setCreated(creation);
                        user.setRoles(new_roles);
                        if (has_restriction) {
                            user.addRestriction(new LockedAccessRestriction());
                        }

                        log(Level.DEBUG, "Creating new user " + user.getUsername());
                        USER_SERVICE.systemCreateUser(user);
                        created++;
                    } else {
                        log(Level.ERROR, "Namesake '" + username + "' detected!");
                    }
                } catch (RootNotModifiableException e) {
                } // Ignored exception
                catch (RequiredFieldMissingException ex) {
                    log(Level.ERROR, "Cannot create user '" + username + "'", ex);
                } catch (IOException | ODataException ex) {
                    log(Level.ERROR, "OData failure on user '" + username + "'", ex);
                }

                this.skip++;
            }

            // This is the end, resets `skip` to 0
            if (userfeed.getEntries().size() < pageSize) {
                this.skip = 0;
            }
        } catch (IOException | ODataException ex) {
            log(Level.ERROR, "OData failure", ex);
        } catch (LockAcquisitionException | CannotAcquireLockException e) {
            throw new InterruptedException(e.getMessage());
        } finally {
            StringBuilder sb = new StringBuilder("done:    ");
            sb.append(created).append(" new Users,    ");
            sb.append(updated).append(" updated Users,    ");
            sb.append("    from ").append(this.client.getServiceRoot());
            log(Level.INFO, sb.toString());

            this.syncConf.setConfig("skip", String.valueOf(skip));
            SYNC_SERVICE.saveSynchronizer(this);
        }
        return false;
    }

    @Override
    public String toString() {
        return "OData User Synchronizer on " + syncConf.getConfig("service_uri");
    }

}