org.onebusaway.admin.service.server.impl.BundleServerServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.onebusaway.admin.service.server.impl.BundleServerServiceImpl.java

Source

/**
 * Copyright (C) 2015 Cambridge Systematics, Inc.
 *
 * 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 org.onebusaway.admin.service.server.impl;

import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.servlet.ServletContext;

import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.MappingJsonFactory;
import org.codehaus.jackson.map.ObjectMapper;
import org.onebusaway.admin.service.RemoteConnectionService;
import org.onebusaway.admin.service.server.BundleServerService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.ServletContextAware;

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.ec2.AmazonEC2Client;
import com.amazonaws.services.ec2.model.DescribeInstancesRequest;
import com.amazonaws.services.ec2.model.DescribeInstancesResult;
import com.amazonaws.services.ec2.model.Instance;
import com.amazonaws.services.ec2.model.InstanceStateChange;
import com.amazonaws.services.ec2.model.StartInstancesRequest;
import com.amazonaws.services.ec2.model.StartInstancesResult;
import com.amazonaws.services.ec2.model.StopInstancesRequest;
import com.amazonaws.services.ec2.model.StopInstancesResult;

public class BundleServerServiceImpl implements BundleServerService, ServletContextAware {

    private static Logger _log = LoggerFactory.getLogger(BundleServerServiceImpl.class);
    private static final String PING_API = "/ping/remote";
    private static final String LOCAL_HOST = "localhost";
    private static final String DEFAULT_CONTEXT = "onebusaway-admin-webapp/api";
    private RemoteConnectionService remoteConnectionService;

    private AWSCredentials _credentials;
    private AmazonEC2Client _ec2;
    private final ObjectMapper _mapper = new ObjectMapper();

    private String _username;
    private String _password;
    private String _context = DEFAULT_CONTEXT;
    private boolean _isAws = false;
    private int _adminPort = 8080;

    @Override
    public void setEc2User(String user) {
        _username = user;
    }

    @Override
    public void setEc2Password(String password) {
        _password = password;
    }

    @PostConstruct
    @Override
    public void setup() {
        _mapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        try {
            if (_isAws) {
                _credentials = new BasicAWSCredentials(_username, _password);
                _ec2 = new AmazonEC2Client(_credentials);
            } else {
                _log.info("setup preferring local config");
            }
        } catch (Throwable t) {
            _log.error("BundleServerServiceImpl setup failed, likely due to missing or invalid credentials");
            _log.error("BundleServerService setup failed:", t);
        }

    }

    @Override
    public void setServletContext(ServletContext servletContext) {
        if (servletContext != null) {
            String user = servletContext.getInitParameter("ec2.user");
            if (user != null) {
                _isAws = true;
                setEc2User(user);
            }
            String password = servletContext.getInitParameter("ec2.password");
            if (password != null) {
                _isAws = true;
                setEc2Password(password);
            }
            String context = servletContext.getInitParameter("admin.context");
            if (context != null) {
                _log.info("using context=" + context);
                _context = context;
            }
            String adminPortStr = servletContext.getInitParameter("admin.port");
            if (adminPortStr != null) {
                _log.info("using admin port=" + adminPortStr);
                _adminPort = Integer.parseInt(adminPortStr);
            }

        }
    }

    @Override
    public String start(String instanceId) {
        if (!_isAws) {
            return LOCAL_HOST;
        }

        if (LOCAL_HOST.equalsIgnoreCase(instanceId)) {
            return instanceId;
        }

        List<String> instances = new ArrayList<String>();
        instances.add(instanceId);
        StartInstancesRequest startInstancesRequest = new StartInstancesRequest(instances);
        StartInstancesResult startInstancesResult = _ec2.startInstances(startInstancesRequest);
        InstanceStateChange change = null;
        if (!startInstancesResult.getStartingInstances().isEmpty()) {
            change = startInstancesResult.getStartingInstances().get(0);
            _log.info("from state=" + change.getPreviousState() + " to state=" + change.getCurrentState());
            return change.getInstanceId();
        }
        return null;
    }

    private Instance getInstance(String instanceId) {
        try {
            DescribeInstancesRequest describeInstancesRequest = new DescribeInstancesRequest();
            List<String> list = new ArrayList<String>();
            list.add(instanceId);
            describeInstancesRequest.setInstanceIds(list);
            // this call can timeout and throw AmazonClientException if AWS is having
            // a bad day
            DescribeInstancesResult result = _ec2.describeInstances(describeInstancesRequest);
            if (!result.getReservations().isEmpty()) {
                if (!result.getReservations().get(0).getInstances().isEmpty()) {
                    Instance i = result.getReservations().get(0).getInstances().get(0);
                    return i;
                }
            }
        } catch (Exception e) {
            _log.error("Call to AWS threw exception", e);
        }
        return null;
    }

    public String pollPublicDns(String instanceId, int maxWaitSeconds) {
        try {
            int count = 0;
            String dns = findPublicDns(instanceId);
            while ((dns == null || dns.length() == 0) && count < maxWaitSeconds) {
                Thread.sleep(1000);
                dns = findPublicDns(instanceId);
                count++;
            }
            return dns;
        } catch (InterruptedException ie) {
            return null;
        }
    }

    @Override
    public String findPublicDns(String instanceId) {
        if (LOCAL_HOST.equalsIgnoreCase(instanceId)) {
            return instanceId;
        }

        Instance i = getInstance(instanceId);
        if (i != null && i.getPublicDnsName() != null) {
            return i.getPublicDnsName();
        }
        if (i != null && !i.getNetworkInterfaces().isEmpty()) {

            // if you need public IP, you need to lookup the association
            return i.getNetworkInterfaces().get(0).getPrivateDnsName();
        }
        return null;
    }

    @Override
    public String findPublicIp(String instanceId) {
        if (LOCAL_HOST.equalsIgnoreCase(instanceId)) {
            return instanceId;
        }

        Instance i = getInstance(instanceId);
        if (i != null && i.getPublicIpAddress() != null) {
            return i.getPublicDnsName();
        }
        if (i != null && !i.getNetworkInterfaces().isEmpty()) {
            if (i.getNetworkInterfaces().get(0).getAssociation() != null) {
                return i.getNetworkInterfaces().get(0).getAssociation().getPublicIp();
            }
        }
        return null;
    }

    @Override
    public String stop(String instanceId) {
        if (!_isAws) {
            return LOCAL_HOST;
        }
        if (LOCAL_HOST.equalsIgnoreCase(instanceId)) {
            return instanceId;
        }

        List<String> instances = new ArrayList<String>();
        instances.add(instanceId);
        StopInstancesRequest stopInstancesRequest = new StopInstancesRequest(instances);
        StopInstancesResult stopInstancesResult = _ec2.stopInstances(stopInstancesRequest);
        InstanceStateChange change = null;
        if (!stopInstancesResult.getStoppingInstances().isEmpty()) {
            change = stopInstancesResult.getStoppingInstances().get(0);
            _log.info("from state=" + change.getPreviousState() + " to state=" + change.getCurrentState());
            return change.getInstanceId();
        }
        return null;
    }

    @Override
    public boolean ping(String instanceId) {
        String json = (String) makeRequestInternal(instanceId, PING_API, null, String.class);
        _log.debug("json=" + json);
        if (json != null)
            json = json.trim();
        return "{1}".equals(json);
    }

    private String generateUrl(String host, String apiCall) {
        if (LOCAL_HOST.equalsIgnoreCase(host)) {
            String url = "http://" + host + ":" + _adminPort + "/" + _context + apiCall;
            _log.debug("generateUrl=" + url + " (use admin.context to override)");
            return url;
        }
        return "http://" + host + ":" + _adminPort + "/api" + apiCall;
    }

    protected <T> T makeRequestInternal(String instanceId, String apiCall, String jsonPayload,
            Class<T> returnType) {
        return makeRequestInternal(instanceId, apiCall, jsonPayload, returnType, null, null);
    }

    protected <T> T makeRequestInternal(String instanceId, String apiCall, String jsonPayload, Class<T> returnType,
            Map<String, String> params, String sessionId) {
        _log.debug("makeRequestInternal(" + instanceId + ", " + apiCall + ")");
        String host = this.findPublicDns(instanceId);
        if (host == null || host.length() == 0) {
            _log.error("makeRequest called with unknown instanceId=" + instanceId);
            return null;
        }

        String url = generateUrl(host, apiCall);
        _log.debug("making request for " + url);

        // copy stream into String
        String content;
        if (params == null)
            content = remoteConnectionService.getContent(url, sessionId);
        else
            content = remoteConnectionService.postContent(url, params, sessionId);

        if (content == null)
            return null;

        // parse content to appropriate return type
        T t = null;
        if (returnType == String.class) {
            t = (T) content;
        } else {
            String json = content;
            try {
                t = _mapper.readValue(json, returnType);
            } catch (JsonParseException e) {
                _log.error("Error parsing json content : " + e);
                e.printStackTrace();
            } catch (JsonMappingException e) {
                _log.error("Error mapping parsed json content : " + e);
                e.printStackTrace();
            } catch (IOException e) {
                _log.error("Error parsing json content : " + e);
                e.printStackTrace();
            }
        }
        _log.debug("got |" + t + "|");
        return t;
    }

    @Override
    public <T> T makeRequest(String instanceId, String apiCall, Object payload, Class<T> returnType,
            int waitTimeInSeconds, Map params, String sessionId) {
        try {
            // serialize payload
            String jsonPayload = toJson(payload);

            // wait for it to answer pings
            int count = 0;
            boolean isAlive = ping(instanceId);
            while (!isAlive && count < waitTimeInSeconds) {
                count++;
                Thread.sleep(5 * 1000);
                isAlive = ping(instanceId);
            }

            if (!isAlive) {
                _log.error("instanceId=" + instanceId + " failed to start");
                return null;
            }
            // make our request
            return (T) makeRequestInternal(instanceId, apiCall, jsonPayload, returnType, params, sessionId);
        } catch (InterruptedException ie) {
            return null;
        } finally {
            _log.debug("exiting makeRequest");
        }
    }

    @Override
    public <T> T makeRequest(String instanceId, String apiCall, Object payload, Class<T> returnType,
            int waitTimeInSeconds, Map params) {
        return makeRequest(instanceId, apiCall, payload, returnType, waitTimeInSeconds, params, null);
    }

    @Override
    public <T> T makeRequest(String instanceId, String apiCall, Object payload, Class<T> returnType,
            int waitTimeInSeconds) {
        return makeRequest(instanceId, apiCall, payload, returnType, waitTimeInSeconds, null);
    }

    private String toJson(Object payload) {
        String jsonPayload = null;
        if (payload != null) {
            StringWriter sw = new StringWriter();
            try {
                final MappingJsonFactory jsonFactory = new MappingJsonFactory();
                final JsonGenerator jsonGenerator = jsonFactory.createJsonGenerator(sw);
                _mapper.writeValue(jsonGenerator, payload);
            } catch (Exception any) {
                _log.error("json execption=", any);
            }
            jsonPayload = sw.toString();
        }
        return jsonPayload;
    }

    /**
     * Injects RemoteConnectionService
     * @param remoteConnectionService the remoteConnectionService to set
     */
    @Autowired
    public void setRemoteConnectionService(RemoteConnectionService remoteConnectionService) {
        this.remoteConnectionService = remoteConnectionService;
    }
}