org.jumpmind.symmetric.service.impl.RegistrationService.java Source code

Java tutorial

Introduction

Here is the source code for org.jumpmind.symmetric.service.impl.RegistrationService.java

Source

/**
 * Licensed to JumpMind Inc under one or more contributor
 * license agreements.  See the NOTICE file distributed
 * with this work for additional information regarding
 * copyright ownership.  JumpMind Inc licenses this file
 * to you under the GNU General Public License, version 3.0 (GPLv3)
 * (the "License"); you may not use this file except in compliance
 * with the License.
 *
 * You should have received a copy of the GNU General Public License,
 * version 3.0 (GPLv3) along with this library; if not, see
 * <http://www.gnu.org/licenses/>.
 *
 * 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.jumpmind.symmetric.service.impl;

import java.io.IOException;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.UnknownHostException;
import java.sql.Types;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateUtils;
import org.jumpmind.db.sql.ISqlRowMapper;
import org.jumpmind.db.sql.ISqlTransaction;
import org.jumpmind.db.sql.Row;
import org.jumpmind.db.sql.mapper.StringMapper;
import org.jumpmind.symmetric.ISymmetricEngine;
import org.jumpmind.symmetric.common.Constants;
import org.jumpmind.symmetric.common.ParameterConstants;
import org.jumpmind.symmetric.config.INodeIdCreator;
import org.jumpmind.symmetric.model.Node;
import org.jumpmind.symmetric.model.NodeGroupLink;
import org.jumpmind.symmetric.model.NodeSecurity;
import org.jumpmind.symmetric.model.RegistrationRequest;
import org.jumpmind.symmetric.model.RegistrationRequest.RegistrationStatus;
import org.jumpmind.symmetric.model.RemoteNodeStatus.Status;
import org.jumpmind.symmetric.security.INodePasswordFilter;
import org.jumpmind.symmetric.service.IConfigurationService;
import org.jumpmind.symmetric.service.IDataExtractorService;
import org.jumpmind.symmetric.service.IDataLoaderService;
import org.jumpmind.symmetric.service.IDataService;
import org.jumpmind.symmetric.service.IExtensionService;
import org.jumpmind.symmetric.service.INodeService;
import org.jumpmind.symmetric.service.IOutgoingBatchService;
import org.jumpmind.symmetric.service.IRegistrationService;
import org.jumpmind.symmetric.service.RegistrationFailedException;
import org.jumpmind.symmetric.service.RegistrationNotOpenException;
import org.jumpmind.symmetric.service.RegistrationRedirectException;
import org.jumpmind.symmetric.statistic.IStatisticManager;
import org.jumpmind.symmetric.transport.ConnectionRejectedException;
import org.jumpmind.symmetric.transport.ITransportManager;
import org.jumpmind.util.AppUtils;
import org.jumpmind.util.RandomTimeSlot;

/**
 * @see IRegistrationService
 */
public class RegistrationService extends AbstractService implements IRegistrationService {

    private INodeService nodeService;

    private IDataExtractorService dataExtractorService;

    private IDataService dataService;

    private IDataLoaderService dataLoaderService;

    private ITransportManager transportManager;

    private IOutgoingBatchService outgoingBatchService;

    private RandomTimeSlot randomTimeSlot;

    private IStatisticManager statisticManager;

    private IConfigurationService configurationService;

    private IExtensionService extensionService;

    private ISymmetricEngine engine;

    public RegistrationService(ISymmetricEngine engine) {
        super(engine.getParameterService(), engine.getSymmetricDialect());
        this.engine = engine;
        this.nodeService = engine.getNodeService();
        this.dataExtractorService = engine.getDataExtractorService();
        this.dataService = engine.getDataService();
        this.dataLoaderService = engine.getDataLoaderService();
        this.transportManager = engine.getTransportManager();
        this.statisticManager = engine.getStatisticManager();
        this.configurationService = engine.getConfigurationService();
        this.outgoingBatchService = engine.getOutgoingBatchService();
        this.extensionService = engine.getExtensionService();
        this.randomTimeSlot = new RandomTimeSlot(parameterService.getExternalId(), 30);
        setSqlMap(new RegistrationServiceSqlMap(symmetricDialect.getPlatform(), createSqlReplacementTokens()));
    }

    public Node registerPullOnlyNode(String externalId, String nodeGroupId, String databaseType,
            String databaseVersion) throws IOException {

        Node node = new Node();
        node.setExternalId(externalId);
        node.setNodeGroupId(nodeGroupId);
        node.setDatabaseType(databaseType);
        node.setDatabaseVersion(databaseVersion);

        node = processRegistration(node, null, null, true, Constants.DEPLOYMENT_TYPE_REST);

        if (node.isSyncEnabled()) {
            //set the node as registered as we have no 
            //virtual batch for registration to be ok'd
            markNodeAsRegistered(node.getNodeId());
        }
        return node;
    }

    public boolean registerNode(Node preRegisteredNode, OutputStream out, boolean isRequestedRegistration)
            throws IOException {
        return registerNode(preRegisteredNode, null, null, out, isRequestedRegistration);
    }

    protected void extractConfiguration(OutputStream out, Node registeredNode) {
        dataExtractorService.extractConfigurationStandalone(registeredNode, out);
    }

    protected Node processRegistration(Node nodePriorToRegistration, String remoteHost, String remoteAddress,
            boolean isRequestedRegistration, String deploymentType) throws IOException {

        Node processedNode = new Node();
        processedNode.setSyncEnabled(false);
        Node identity = nodeService.findIdentity();
        if (identity == null) {
            RegistrationRequest req = new RegistrationRequest(nodePriorToRegistration, RegistrationStatus.ER,
                    remoteHost, remoteAddress);
            req.setErrorMessage("Cannot register a client node until this node is registered");
            saveRegistrationRequest(req);
            log.warn(req.getErrorMessage());
            return processedNode;
        }

        try {
            if (!nodeService.isRegistrationServer()) {
                /*
                 * registration is not allowed until this node has an identity
                 * and an initial load
                 */
                NodeSecurity security = nodeService.findNodeSecurity(identity.getNodeId());
                if (security == null || security.getInitialLoadTime() == null) {
                    RegistrationRequest req = new RegistrationRequest(nodePriorToRegistration,
                            RegistrationStatus.ER, remoteHost, remoteAddress);
                    req.setErrorMessage(
                            "Cannot register a client node until this node has an initial load (ie. node_security.initial_load_time is a non null value)");
                    saveRegistrationRequest(req);
                    log.warn(req.getErrorMessage());
                    return processedNode;
                }
            }

            String redirectUrl = getRedirectionUrlFor(nodePriorToRegistration.getExternalId());
            if (redirectUrl != null) {
                log.info("Redirecting {} to {} for registration.", nodePriorToRegistration.getExternalId(),
                        redirectUrl);
                saveRegistrationRequest(new RegistrationRequest(nodePriorToRegistration, RegistrationStatus.RR,
                        remoteHost, remoteAddress));
                throw new RegistrationRedirectException(redirectUrl);
            }

            /*
             * Check to see if there is a link that exists to service the node
             * that is requesting registration
             */
            NodeGroupLink link = configurationService.getNodeGroupLinkFor(identity.getNodeGroupId(),
                    nodePriorToRegistration.getNodeGroupId(), false);
            if (link == null
                    && parameterService.is(ParameterConstants.REGISTRATION_REQUIRE_NODE_GROUP_LINK, true)) {
                RegistrationRequest req = new RegistrationRequest(nodePriorToRegistration, RegistrationStatus.ER,
                        remoteHost, remoteAddress);
                req.setErrorMessage(String.format(
                        "Cannot register a client node unless a node group link exists so the registering node can receive configuration updates.  Please add a group link where the source group id is %s and the target group id is %s",
                        identity.getNodeGroupId(), nodePriorToRegistration.getNodeGroupId()));
                saveRegistrationRequest(req);
                log.warn(req.getErrorMessage());
                return processedNode;
            }

            String nodeId = StringUtils.isBlank(nodePriorToRegistration.getNodeId())
                    ? extensionService.getExtensionPoint(INodeIdCreator.class).selectNodeId(nodePriorToRegistration,
                            remoteHost, remoteAddress)
                    : nodePriorToRegistration.getNodeId();

            Node foundNode = nodeService.findNode(nodeId);
            NodeSecurity security = nodeService.findNodeSecurity(nodeId);

            if ((foundNode == null || security == null || !security.isRegistrationEnabled())
                    && parameterService.is(ParameterConstants.AUTO_REGISTER_ENABLED)) {
                openRegistration(nodePriorToRegistration, remoteHost, remoteAddress);
                nodeId = StringUtils.isBlank(nodePriorToRegistration.getNodeId())
                        ? extensionService.getExtensionPoint(INodeIdCreator.class)
                                .selectNodeId(nodePriorToRegistration, remoteHost, remoteAddress)
                        : nodePriorToRegistration.getNodeId();
                security = nodeService.findNodeSecurity(nodeId);
                foundNode = nodeService.findNode(nodeId);
            } else if (foundNode == null || security == null || !security.isRegistrationEnabled()) {
                saveRegistrationRequest(new RegistrationRequest(nodePriorToRegistration, RegistrationStatus.RQ,
                        remoteHost, remoteAddress));
                return processedNode;
            }

            foundNode.setSyncEnabled(true);
            if (Constants.DEPLOYMENT_TYPE_REST.equalsIgnoreCase(deploymentType)) {
                foundNode.setSymmetricVersion(null);
                foundNode.setDeploymentType(deploymentType);
            }
            foundNode.setSyncUrl(nodePriorToRegistration.getSyncUrl());
            foundNode.setDatabaseType(nodePriorToRegistration.getDatabaseType());
            foundNode.setDatabaseVersion(nodePriorToRegistration.getDatabaseVersion());
            foundNode.setSymmetricVersion(nodePriorToRegistration.getSymmetricVersion());
            nodeService.save(foundNode);

            /**
             * Only send automatic initial load once or if the client is really
             * re-registering
             */
            if ((security != null && security.getInitialLoadTime() == null) || isRequestedRegistration) {
                if (parameterService.is(ParameterConstants.AUTO_RELOAD_ENABLED)) {
                    nodeService.setInitialLoadEnabled(nodeId, true, false, -1, "registration");
                }

                if (parameterService.is(ParameterConstants.AUTO_RELOAD_REVERSE_ENABLED)) {
                    nodeService.setReverseInitialLoadEnabled(nodeId, true, false, -1, "registration");
                }
            }

            saveRegistrationRequest(
                    new RegistrationRequest(foundNode, RegistrationStatus.OK, remoteHost, remoteAddress));

            statisticManager.incrementNodesRegistered(1);

            return foundNode;

        } catch (RegistrationNotOpenException ex) {
            if (StringUtils.isNotBlank(ex.getMessage())) {
                log.warn("Registration not allowed for {} because {}", nodePriorToRegistration.toString(),
                        ex.getMessage());
            }
            return processedNode;
        }
    }

    /**
     * @see IRegistrationService#registerNode(Node, OutputStream, boolean)
     */
    public boolean registerNode(Node nodePriorToRegistration, String remoteHost, String remoteAddress,
            OutputStream out, boolean isRequestedRegistration) throws IOException {

        Node processedNode = processRegistration(nodePriorToRegistration, remoteHost, remoteAddress,
                isRequestedRegistration, null);

        if (processedNode.isSyncEnabled()) {
            /*
             * Mark all configuration batches as processed because we are about to reload
             * the configuration for the node
             */
            outgoingBatchService.markAllConfigAsSentForNode(processedNode.getNodeId());
            extractConfiguration(out, processedNode);
        }

        return processedNode.isSyncEnabled();
    }

    public List<RegistrationRequest> getRegistrationRequests(boolean includeNodesWithOpenRegistrations) {
        List<RegistrationRequest> requests = sqlTemplate.query(getSql("selectRegistrationRequestSql"),
                new RegistrationRequestMapper());
        if (!includeNodesWithOpenRegistrations) {
            Collection<Node> nodes = nodeService.findNodesWithOpenRegistration();
            Iterator<RegistrationRequest> i = requests.iterator();
            while (i.hasNext()) {
                RegistrationRequest registrationRequest = (RegistrationRequest) i.next();
                for (Node node : nodes) {
                    if (node.getNodeGroupId().equals(registrationRequest.getNodeGroupId())
                            && node.getExternalId().equals(registrationRequest.getExternalId())) {
                        i.remove();
                    }
                }
            }
        }
        return requests;
    }

    public boolean deleteRegistrationRequest(RegistrationRequest request) {
        String externalId = request.getExternalId() == null ? "" : request.getExternalId();
        String nodeGroupId = request.getNodeGroupId() == null ? "" : request.getNodeGroupId();
        return 0 < sqlTemplate.update(getSql("deleteRegistrationRequestSql"), new Object[] { nodeGroupId,
                externalId, request.getIpAddress(), request.getHostName(), request.getStatus().name() });
    }

    public void saveRegistrationRequest(RegistrationRequest request) {
        /**
         * Lookup existing registration requests to update the attempt count.  We previously
         * did this in SQL on the update, but as400 v5 didn't like that
         */
        boolean foundOne = false;
        List<RegistrationRequest> requests = getRegistrationRequests(true);
        for (RegistrationRequest registrationRequest : requests) {
            if (registrationRequest.getNodeGroupId().equals(request.getNodeGroupId())
                    && registrationRequest.getExternalId().equals(request.getExternalId())) {
                request.setAttemptCount(registrationRequest.getAttemptCount() + 1);
                foundOne = true;
                break;
            }
        }
        String externalId = request.getExternalId() == null ? "" : request.getExternalId();
        String nodeGroupId = request.getNodeGroupId() == null ? "" : request.getNodeGroupId();
        int count = 0;
        if (foundOne) {
            count = sqlTemplate.update(getSql("updateRegistrationRequestSql"),
                    new Object[] { request.getAttemptCount(), request.getLastUpdateBy(),
                            request.getLastUpdateTime(), request.getRegisteredNodeId(), request.getStatus().name(),
                            request.getErrorMessage(), nodeGroupId, externalId, request.getIpAddress(),
                            request.getHostName() },
                    new int[] { Types.NUMERIC, Types.VARCHAR, Types.DATE, Types.VARCHAR, Types.VARCHAR,
                            Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR });
        }

        if (count == 0) {
            sqlTemplate.update(getSql("insertRegistrationRequestSql"),
                    new Object[] { request.getLastUpdateBy(), request.getLastUpdateTime(),
                            request.getRegisteredNodeId(), request.getStatus().name(), nodeGroupId, externalId,
                            request.getIpAddress(), request.getHostName(), request.getErrorMessage() },
                    new int[] { Types.VARCHAR, Types.DATE, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
                            Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR });
        }

    }

    public String getRedirectionUrlFor(String externalId) {
        List<String> list = sqlTemplate.query(getSql("getRegistrationRedirectUrlSql"), new StringMapper(),
                new Object[] { externalId }, new int[] { Types.VARCHAR });
        if (list.size() > 0) {
            return transportManager.resolveURL(list.get(0), parameterService.getRegistrationUrl());
        } else {
            return null;
        }
    }

    public void saveRegistrationRedirect(String externalIdToRedirect, String nodeIdToRedirectTo) {
        int count = sqlTemplate.update(getSql("updateRegistrationRedirectUrlSql"),
                new Object[] { nodeIdToRedirectTo, externalIdToRedirect },
                new int[] { Types.VARCHAR, Types.VARCHAR });
        if (count == 0) {
            sqlTemplate.update(getSql("insertRegistrationRedirectUrlSql"),
                    new Object[] { nodeIdToRedirectTo, externalIdToRedirect },
                    new int[] { Types.VARCHAR, Types.VARCHAR });
        }
    }

    /**
     * @see IRegistrationService#markNodeAsRegistered(Node)
     */
    public void markNodeAsRegistered(String nodeId) {
        ISqlTransaction transaction = null;
        try {
            transaction = sqlTemplate.startSqlTransaction();
            symmetricDialect.disableSyncTriggers(transaction, nodeId);
            transaction.prepareAndExecute(getSql("registerNodeSecuritySql"), nodeId);
            transaction.commit();
        } catch (Error ex) {
            if (transaction != null) {
                transaction.rollback();
            }
            throw ex;
        } catch (RuntimeException ex) {
            if (transaction != null) {
                transaction.rollback();
            }
            throw ex;
        } finally {
            symmetricDialect.enableSyncTriggers(transaction);
            close(transaction);
        }
    }

    private void sleepBeforeRegistrationRetry() {
        long sleepTimeInMs = DateUtils.MILLIS_PER_SECOND * randomTimeSlot.getRandomValueSeededByExternalId();
        log.info("Could not register.  Sleeping before attempting again.", sleepTimeInMs);
        log.info("Sleeping for {}ms", sleepTimeInMs);
        AppUtils.sleep(sleepTimeInMs);
    }

    public boolean isRegisteredWithServer() {
        return nodeService.findIdentity() != null;
    }

    /**
     * @see IRegistrationService#registerWithServer()
     */
    public void registerWithServer() {
        boolean registered = isRegisteredWithServer();
        int maxNumberOfAttempts = parameterService.getInt(ParameterConstants.REGISTRATION_NUMBER_OF_ATTEMPTS);
        while (!registered && (maxNumberOfAttempts < 0 || maxNumberOfAttempts > 0) && engine.isStarted()) {
            try {
                log.info("This node is unregistered.  It will attempt to register using the registration.url");
                registered = dataLoaderService.loadDataFromPull(null).getStatus() == Status.DATA_PROCESSED;
            } catch (ConnectException e) {
                log.warn("The request to register failed because the client failed to connect to the server");
            } catch (UnknownHostException e) {
                log.warn("The request to register failed because the host was unknown");
            } catch (ConnectionRejectedException ex) {
                log.warn(
                        "The request to register was rejected by the server.  Either the server node is not started, the server is not configured properly or the registration url is incorrect");
            } catch (Exception e) {
                log.error("", e);
            }

            maxNumberOfAttempts--;

            if (!registered && (maxNumberOfAttempts < 0 || maxNumberOfAttempts > 0)) {
                registered = isRegisteredWithServer();
                if (registered) {
                    log.info(
                            "We registered, but were not able to acknowledge our registration.  Sending a sql event to the node where we registered to indicate that we are alive and registered");
                    Node identity = nodeService.findIdentity();
                    Node parentNode = nodeService.findNode(identity.getCreatedAtNodeId());
                    dataService.insertSqlEvent(parentNode, "update " + tablePrefix
                            + "_node_security set registration_enabled=1, registration_time=current_timestamp where node_id='"
                            + identity.getNodeId() + "'", false, -1, null);
                }
            }

            if (registered) {
                Node node = nodeService.findIdentity();
                if (node != null) {
                    log.info("Successfully registered node [id={}]", node.getNodeId());
                    dataService.heartbeat(true);
                } else {
                    log.error(
                            "Node identity is missing after registration.  The registration server may be misconfigured or have an error");
                    registered = false;
                }
            }

            if (!registered && maxNumberOfAttempts != 0) {
                sleepBeforeRegistrationRetry();
            }
        }

        if (!registered) {
            throw new RegistrationFailedException(String.format("Failed after trying to register %s times.",
                    parameterService.getString(ParameterConstants.REGISTRATION_NUMBER_OF_ATTEMPTS)));
        }
    }

    /**
     * @see IRegistrationService#reOpenRegistration(String)
     */
    public synchronized void reOpenRegistration(String nodeId) {
        Node node = nodeService.findNode(nodeId);
        NodeSecurity security = nodeService.findNodeSecurity(nodeId);
        String password = null;
        if (security != null
                && parameterService.is(ParameterConstants.REGISTRATION_REOPEN_USE_SAME_PASSWORD, true)) {
            password = security.getNodePassword();
        } else {
            password = extensionService.getExtensionPoint(INodeIdCreator.class).generatePassword(node);
            password = filterPasswordOnSaveIfNeeded(password);
        }
        if (node != null) {
            int updateCount = sqlTemplate.update(getSql("reopenRegistrationSql"),
                    new Object[] { password, nodeId });
            if (updateCount == 0 && nodeService.findNodeSecurity(nodeId) == null) {
                // if the update count was 0, then we probably have a row in the
                // node table, but not in node security.
                // lets go ahead and try to insert into node security.
                sqlTemplate.update(getSql("openRegistrationNodeSecuritySql"),
                        new Object[] { nodeId, password, nodeService.findNode(nodeId).getNodeId() });
                log.info("Registration was opened for {}", nodeId);
            } else if (updateCount == 0) {
                log.warn("Registration was already enabled for {}.  No need to reenable it", nodeId);
            } else {
                log.info("Registration was reopened for {}", nodeId);
            }
        } else {
            log.warn("There was no row with a node id of {} to 'reopen' registration for", nodeId);
        }
    }

    /**
     * @see IRegistrationService#openRegistration(String, String)
     * @return The nodeId of the registered node
     */
    public synchronized String openRegistration(String nodeGroup, String externalId) {
        Node node = new Node();
        node.setExternalId(externalId);
        node.setNodeGroupId(nodeGroup);
        return openRegistration(node);
    }

    public synchronized String openRegistration(String nodeGroup, String externalId, String remoteHost,
            String remoteAddress) {
        Node node = new Node();
        node.setExternalId(externalId);
        node.setNodeGroupId(nodeGroup);
        return openRegistration(node, remoteHost, remoteAddress);
    }

    public synchronized String openRegistration(Node node) {
        return openRegistration(node, null, null);
    }

    protected String openRegistration(Node node, String remoteHost, String remoteAddress) {
        Node me = nodeService.findIdentity();
        if (me != null) {
            String nodeId = extensionService.getExtensionPoint(INodeIdCreator.class).generateNodeId(node,
                    remoteHost, remoteAddress);
            Node existingNode = nodeService.findNode(nodeId);
            if (existingNode == null) {
                node.setNodeId(nodeId);
                node.setSyncEnabled(false);

                boolean masterToMaster = configurationService.isMasterToMaster();
                node.setCreatedAtNodeId(masterToMaster ? null : me.getNodeId());
                nodeService.save(node);

                // make sure there isn't a node security row lying around w/out
                // a node row
                nodeService.deleteNodeSecurity(nodeId);
                String password = extensionService.getExtensionPoint(INodeIdCreator.class).generatePassword(node);
                password = filterPasswordOnSaveIfNeeded(password);
                sqlTemplate.update(getSql("openRegistrationNodeSecuritySql"),
                        new Object[] { nodeId, password, masterToMaster ? null : me.getNodeId() });
                nodeService.insertNodeGroup(node.getNodeGroupId(), null);
                log.info(
                        "Just opened registration for external id of {} and a node group of {} and a node id of {}",
                        new Object[] { node.getExternalId(), node.getNodeGroupId(), nodeId });
            } else {
                reOpenRegistration(nodeId);
            }
            return nodeId;
        } else {
            throw new IllegalStateException(
                    "This node has not been configured.  Could not find a row in the identity table");
        }
    }

    public boolean isAutoRegistration() {
        return parameterService.is(ParameterConstants.AUTO_REGISTER_ENABLED);
    }

    private String filterPasswordOnSaveIfNeeded(String password) {
        String s = password;
        INodePasswordFilter nodePasswordFilter = extensionService.getExtensionPoint(INodePasswordFilter.class);
        if (nodePasswordFilter != null) {
            s = nodePasswordFilter.onNodeSecuritySave(password);
        }
        return s;
    }

    public boolean isRegistrationOpen(String nodeGroupId, String externalId) {
        Node node = nodeService.findNodeByExternalId(nodeGroupId, externalId);
        if (node != null) {
            NodeSecurity security = nodeService.findNodeSecurity(node.getNodeId());
            return security != null && security.isRegistrationEnabled();
        }
        return false;
    }

    public void requestNodeCopy() {
        Node copyFrom = nodeService.findIdentity();
        if (copyFrom == null) {
            throw new IllegalStateException("No identity found.  Can only copy if the node has an identity");
        }
        boolean copied = false;
        int maxNumberOfAttempts = parameterService.getInt(ParameterConstants.REGISTRATION_NUMBER_OF_ATTEMPTS);
        while (!copied && (maxNumberOfAttempts < 0 || maxNumberOfAttempts > 0)) {

            try {
                log.info(
                        "Detected that node '{}' should be copied to a new node id.  Attempting to contact server to accomplish this",
                        copyFrom.getNodeId());
                copied = transportManager.sendCopyRequest(copyFrom) == HttpURLConnection.HTTP_OK;
                if (copied) {
                    nodeService.deleteIdentity();
                }
            } catch (ConnectException e) {
                log.warn("The request to copy failed because the client failed to connect to the server");
            } catch (UnknownHostException e) {
                log.warn("The request to copy failed because the host was unknown");
            } catch (ConnectionRejectedException ex) {
                log.warn(
                        "The request to copy was rejected by the server.  Either the server node is not started, the server is not configured properly or the registration url is incorrect");
            } catch (Exception e) {
                log.error("", e);
            }

            maxNumberOfAttempts--;

            if (!copied) {
                long sleepTimeInMs = DateUtils.MILLIS_PER_SECOND
                        * randomTimeSlot.getRandomValueSeededByExternalId();
                log.warn("Copy failed.  Sleeping before attempting again.", sleepTimeInMs);
                log.info("Sleeping for {}ms", sleepTimeInMs);
                AppUtils.sleep(sleepTimeInMs);
            }

        }

        if (!copied) {
            throw new RegistrationFailedException(String.format("Failed after trying to copy %s times.",
                    parameterService.getString(ParameterConstants.REGISTRATION_NUMBER_OF_ATTEMPTS)));
        }
    }

    class RegistrationRequestMapper implements ISqlRowMapper<RegistrationRequest> {
        public RegistrationRequest mapRow(Row rs) {
            RegistrationRequest request = new RegistrationRequest();
            request.setNodeGroupId(rs.getString("node_group_id"));
            request.setExternalId(rs.getString("external_id"));
            request.setStatus(RegistrationStatus.valueOf(RegistrationStatus.class, rs.getString("status")));
            request.setHostName(rs.getString("host_name"));
            request.setIpAddress(rs.getString("ip_address"));
            request.setAttemptCount(rs.getLong("attempt_count"));
            request.setRegisteredNodeId(rs.getString("registered_node_id"));
            request.setCreateTime(rs.getDateTime("create_time"));
            request.setLastUpdateBy(rs.getString("last_update_by"));
            request.setLastUpdateTime(rs.getDateTime("last_update_time"));
            request.setErrorMessage(rs.getString("error_message"));
            return request;
        }
    }

}