/* * ProActive Parallel Suite(TM): * The Open Source library for parallel and distributed * Workflows & Scheduling, Orchestration, Cloud Automation * and Big Data Analysis on Enterprise Grids & Clouds. * * Copyright (c) 2007 - 2017 ActiveEon * Contact: contact@activeeon.com * * This library 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: version 3 of * the License. * * 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/>. * * If needed, contact us to obtain a release under GPL Version 2 or 3 * or a different license than the AGPL. */ package org.ow2.proactive.scheduler.rest; import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Strings.isNullOrEmpty; import java.io.IOException; import java.util.Collections; import java.util.List; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import org.apache.log4j.Logger; import org.atmosphere.wasync.Client; import org.atmosphere.wasync.ClientFactory; import org.atmosphere.wasync.Decoder; import org.atmosphere.wasync.Encoder; import org.atmosphere.wasync.Event; import org.atmosphere.wasync.Function; import org.atmosphere.wasync.Request; import org.atmosphere.wasync.RequestBuilder; import org.atmosphere.wasync.Socket; import org.ow2.proactive.scheduler.common.NotificationData; import org.ow2.proactive.scheduler.common.SchedulerEvent; import org.ow2.proactive.scheduler.common.SchedulerEventListener; import org.ow2.proactive.scheduler.common.job.UserIdentification; import org.ow2.proactive.scheduler.rest.data.DataUtility; import org.ow2.proactive.scheduler.rest.utils.EventCodecUtil; import org.ow2.proactive_grid_cloud_portal.scheduler.dto.JobInfoData; import org.ow2.proactive_grid_cloud_portal.scheduler.dto.JobStateData; import org.ow2.proactive_grid_cloud_portal.scheduler.dto.TaskInfoData; import org.ow2.proactive_grid_cloud_portal.scheduler.dto.eventing.EventNotification; import org.ow2.proactive_grid_cloud_portal.scheduler.dto.eventing.EventNotification.Action; import org.ow2.proactive_grid_cloud_portal.scheduler.dto.eventing.EventSubscription; import com.google.common.collect.Lists; /** * Utility class to subscribe and receive scheduler events from REST server. It * utilizes Atmosphere 2.0 framework */ public class SchedulerEventReceiver { private static final String EVENTING_PATH = "scheduler/events"; private static final Logger logger = Logger.getLogger(SchedulerEventReceiver.class); private String restServerUrl; private SchedulerEventListener eventListener; private boolean myEventsOnly; private SchedulerEvent[] events; private String sessionId; private Socket socket; private SchedulerEventReceiver() { } public void start() throws IOException { openAndReceive(eventListener, myEventsOnly, events); } @SuppressWarnings("rawtypes") private void openAndReceive(final SchedulerEventListener eventListener, boolean myEventsOnly, SchedulerEvent... events) throws IOException { Client client = ClientFactory.getDefault().newClient(); socket = client.create(client.newOptionsBuilder().reconnect(false).build()); EventNotificationHandler notificationHandler = new EventNotificationHandler(eventListener); socket.on(Event.MESSAGE, notificationHandler); socket.on(Event.CLOSE, new Function() { public void on(Object t) { SchedulerEventReceiver.logger.info("#### Websocket connection is closed ####"); if (eventListener instanceof DisconnectionAwareSchedulerEventListener) { ((DisconnectionAwareSchedulerEventListener) eventListener).notifyDisconnection(); } } }); // initialize the connection RequestBuilder requestBuilder = client.newRequestBuilder(); requestBuilder.method(Request.METHOD.GET); requestBuilder.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON); // authentication header requestBuilder.header("sessionid", sessionId); requestBuilder.uri(eventingUrl(restServerUrl)); requestBuilder.encoder(new EventSubscriptionEncoder()); requestBuilder.decoder(new EventNotificationDecoder()); requestBuilder.transport(Request.TRANSPORT.WEBSOCKET); socket.open(requestBuilder.build()); // submit subscription request EventSubscription eventSubscription = new EventSubscription(myEventsOnly, asStringArray(events)); socket.fire(EventCodecUtil.toJsonString(eventSubscription)); } public void stop() { if (socket != null) { socket.close(); } } private static List<String> asStringArray(SchedulerEvent... schedulerEvents) { List<String> result; if (schedulerEvents == null) { return Collections.emptyList(); } result = Lists.newArrayListWithCapacity(schedulerEvents.length); for (SchedulerEvent event : schedulerEvents) { result.add(event.name()); } return result; } private static String eventingUrl(String restServerUrl) { return restServerUrl + (restServerUrl.endsWith("/") ? "" : "/") + EVENTING_PATH; } public static class Builder { private SchedulerEventReceiver schedulerEventReceiver = new SchedulerEventReceiver(); public Builder restServerUrl(String restServerUrl) { this.schedulerEventReceiver.restServerUrl = restServerUrl; return this; } public Builder sessionId(String sessionId) { this.schedulerEventReceiver.sessionId = sessionId; return this; } public Builder schedulerEventListener(SchedulerEventListener eventListener) { this.schedulerEventReceiver.eventListener = eventListener; return this; } public Builder myEventsOnly(boolean myEventsOnly) { this.schedulerEventReceiver.myEventsOnly = myEventsOnly; return this; } public Builder selectedEvents(SchedulerEvent... events) { this.schedulerEventReceiver.events = events; return this; } public SchedulerEventReceiver build() { checkState(!isNullOrEmpty(this.schedulerEventReceiver.restServerUrl), "REST Server URL cannot be null or empty"); checkState(this.schedulerEventReceiver.eventListener != null, "Scheduler Event Listener cannot be null."); checkState(!isNullOrEmpty(this.schedulerEventReceiver.sessionId), "Sessiond id cannot be null or empty."); return this.schedulerEventReceiver; } } private static class EventNotificationDecoder implements Decoder<String, EventNotification> { @Override public EventNotification decode(Event event, String message) { EventNotification notification = null; if (Event.MESSAGE == event) { try { notification = EventCodecUtil.fromJsonString(message, EventNotification.class); } catch (Exception e) { logger.error(String.format("Cannot construct %s type object from: %n%s", EventNotification.class.getName(), message), e); } } return notification; } } private static class EventSubscriptionEncoder implements Encoder<EventSubscription, String> { @Override public String encode(EventSubscription subscription) { try { return EventCodecUtil.toJsonString(subscription); } catch (Exception e) { throw new RuntimeException("Cannot construct a JSON string from the specified object.", e); } } } private static class EventNotificationHandler implements Function<EventNotification> { private SchedulerEventListener eventListener; EventNotificationHandler(SchedulerEventListener eventListener) { this.eventListener = eventListener; } @SuppressWarnings("unchecked") @Override public void on(EventNotification eventData) { Action action = eventData.getAction(); switch (action) { case NONE: break; case SCHEDULER_STATE_UPDATED: eventListener.schedulerStateUpdatedEvent(SchedulerEvent.valueOf(eventData.getSchedulerEvent())); break; case JOB_SUBMITTED: eventListener.jobSubmittedEvent(DataUtility.toJobState((JobStateData) eventData.getData())); break; case JOB_STATE_UPDATED: eventListener.jobStateUpdatedEvent(new NotificationData<>(SchedulerEvent.valueOf(eventData.getSchedulerEvent()), DataUtility.toJobInfo((JobInfoData) eventData.getData()))); break; case JOB_FULL_DATA_UPDATED: eventListener.jobUpdatedFullDataEvent(DataUtility.toJobState((JobStateData) eventData.getData())); break; case TASK_STATE_UPDATED: eventListener.taskStateUpdatedEvent(new NotificationData<>(SchedulerEvent.valueOf(eventData.getSchedulerEvent()), DataUtility.taskInfo((TaskInfoData) eventData.getData()))); break; case USERS_UPDATED: eventListener.usersUpdatedEvent((NotificationData<UserIdentification>) eventData.getData()); break; default: throw new RuntimeException(String.format("Unknown action: %s", action)); } } } }