com.surevine.ldap2alfresco.ProfileUpdater.java Source code

Java tutorial

Introduction

Here is the source code for com.surevine.ldap2alfresco.ProfileUpdater.java

Source

/*
 * Copyright (C) 2008-2010 Surevine Limited.
 *   
 * Although intended for deployment and use alongside Alfresco this module should
 * be considered 'Not a Contribution' as defined in Alfresco'sstandard contribution agreement, see
 * http://www.alfresco.org/resource/AlfrescoContributionAgreementv2.pdf
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
package com.surevine.ldap2alfresco;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;

import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttributes;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.json.JSONObject;

import com.surevine.alfresco.AlfrescoConnector;
import com.surevine.alfresco.AlfrescoException;
import com.surevine.alfresco.PropertyException;
import com.surevine.alfresco.PropertyWrapper;

/**
 * Class to update a number of profile fields.
 */
public class ProfileUpdater {

    /**
     * A list of profile fields to update.
     */
    private Collection<ProfileFieldConverter> profileFields;

    /**
     * Logging instance.
     */
    private static final Logger LOGGER = Logger.getLogger(ProfileUpdater.class);

    /**
     * Construct a ProfileUpdater loading the fields to synchronise from a PropertyWrapper.
     * @param properties Where to load the fields from
     * @throws PropertyException if profile fields are not defined correctly in the properties file
     */
    public ProfileUpdater(final PropertyWrapper properties) throws PropertyException {

        profileFields = getProfileFields(properties, "syncField");
    }

    /**
     * Update the profile fields into LDAP for a single user.
     * @param ldap To connect to LDAP
     * @param username User to update
     * @param fields Contains entries for every field listed in properties file
     * @throws LdapException On any LDAP errors
     */
    public void updateSingleUserToLdap(final LdapConnector ldap, final String username, final JSONObject fields)
            throws LdapException {

        Attributes attributes = new BasicAttributes();

        // encode each profile field in turn
        Iterator<ProfileFieldConverter> fieldIter = profileFields.iterator();

        while (fieldIter.hasNext()) {
            ProfileFieldConverter converter = fieldIter.next();
            converter.toAttributes(attributes, fields);
        }

        ldap.updateUser(username, attributes);
    }

    /**
     * Update Alfresco user profile fields from LDAP user attributes.  If any users
     * cannot be updated they are logged and skipped.
     * @param ldap To connect to LDAP
     * @param alfresco To connect to Alfresco
     * @param allUsers Set to true to update all records, not just records changed since the last run
     * @param lastRun Timestamp of the last run (string representation as returned by LDAP server)
     * @throws LdapException On any LDAP errors
     * @throws AlfrescoException On any fatal Alfresco errors
     */
    public void updateFromLdapToAlfresco(final LdapConnector ldap, final AlfrescoConnector alfresco,
            final boolean allUsers, final String lastRun) throws LdapException, AlfrescoException {

        // get groups
        Collection<Attributes> users = null;

        if (allUsers) {
            users = ldap.getAllUsers();
        } else {
            users = ldap.getModifiedUsers(lastRun);
        }

        LOGGER.info("Found " + users.size() + " users to synchronise");

        Iterator<Attributes> userIter = users.iterator();

        while (userIter.hasNext()) {
            // get the user
            Attributes userAttributes = userIter.next();

            Attribute cn = userAttributes.get("cn");

            if (cn != null) {
                String username;

                try {
                    username = cn.get().toString();
                } catch (NamingException e1) {
                    username = null;
                }

                if (username != null) {
                    LOGGER.info("Synchronising " + username);

                    JSONObject fields = new JSONObject();

                    // encode each profile field in turn
                    Iterator<ProfileFieldConverter> fieldIter = profileFields.iterator();

                    while (fieldIter.hasNext()) {
                        ProfileFieldConverter converter = fieldIter.next();
                        converter.toJson(fields, userAttributes);
                    }

                    try {
                        alfresco.updateProfile(username, fields);
                    } catch (AlfrescoException e) {
                        recoverFromExceptionUser(ldap, e, username);
                    }
                }
            }
        }
    }

    /**
     * Return a list of profile fields from the properties file.
     * @param properties Properties to look in
     * @param fieldPrefix Prefix for all properties which represent a profile field
     * @return List of profile field converters
     * @throws PropertyException if profile fields are not defined correctly in the properties file
     */
    private Collection<ProfileFieldConverter> getProfileFields(final PropertyWrapper properties,
            final String fieldPrefix) throws PropertyException {

        final String typeLabel = "type";
        final String multipleLabel = "multiple";
        final String ldapNameLabel = "ldapName";
        final String alfrescoNameLabel = "alfrescoName";

        Collection<ProfileFieldConverter> fields = new LinkedList<ProfileFieldConverter>();

        // looking for e.g. syncField.type.something
        Collection<String> keys = properties.getKeys("^" + fieldPrefix + "\\." + typeLabel + "\\..+");

        Iterator<String> iter = keys.iterator();

        while (iter.hasNext()) {
            String typeKey = iter.next();
            String attributeName = typeKey.replaceFirst("^" + fieldPrefix + "\\." + typeLabel + "\\.", "");

            String type = properties.getProperty(typeKey);

            boolean multiple = properties.getProperty(fieldPrefix + "." + multipleLabel + "." + attributeName)
                    .equalsIgnoreCase("yes");

            String ldapName = properties.getProperty(fieldPrefix + "." + ldapNameLabel + "." + attributeName);

            String alfrescoName = properties
                    .getProperty(fieldPrefix + "." + alfrescoNameLabel + "." + attributeName);

            LOGGER.info("Loading profile field converter: " + ldapName + " -> " + alfrescoName);

            if (type.equalsIgnoreCase("text")) {
                fields.add(new ProfileFieldTextConverter(ldapName, alfrescoName, multiple));
            } else if (type.equalsIgnoreCase("telephone")) {
                fields.add(new ProfileFieldTelephoneConverter(ldapName, alfrescoName, multiple));
            } else {
                LOGGER.log(Level.ERROR, "Unrecognised profile field type: " + type);
            }
        }

        return fields;
    }

    /**
     * Attempt to recover from an exception by updating the timestamp of
     * the user being processed so it gets processed next time.  If that
     * fails then a new exception is thrown, which will cause processing
     * to be aborted, and the next run will identify the failure due to
     * the lock entry still being present and disallow an incremental
     * update.
     * @param ldap To connect to LDAP
     * @param e The exception that caused the problem
     * @param username The LDAP user being processed
     * @throws LdapException If the LDAP user cannot be modified
     */
    private void recoverFromExceptionUser(final LdapConnector ldap, final Exception e, final String username)
            throws LdapException {
        logException(Level.ERROR, e);

        // mark user as modified so it gets tried again next time round
        ldap.touchUser(username);
    }

    /**
     * Output an exception's stack trace to the log file.
     * @param level The log level
     * @param e The exception
     */
    private void logException(final Level level, final Exception e) {
        try {
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            e.printStackTrace(pw);
            LOGGER.log(level, sw.toString());
        } catch (Exception e2) {
            LOGGER.log(level, "stack trace unavailable");
        }
    }
}