/* * * Copyright (C) 2012-2014 R T Huitema. All Rights Reserved. * Web: www.42.co.nz * Email: robert@42.co.nz * Author: R T Huitema * * This file is part of the signalk-server-java project * * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. * * 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 nz.co.fortytwo.signalk.server; import static nz.co.fortytwo.signalk.util.SignalKConstants.dot; import static nz.co.fortytwo.signalk.util.SignalKConstants.source; import static nz.co.fortytwo.signalk.util.SignalKConstants.sourceRef; import static nz.co.fortytwo.signalk.util.SignalKConstants.timestamp; import static nz.co.fortytwo.signalk.util.SignalKConstants.vessels; import io.netty.util.internal.ConcurrentSet; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.regex.Pattern; import nz.co.fortytwo.signalk.model.event.PathEvent; import nz.co.fortytwo.signalk.model.impl.SignalKModelFactory; import nz.co.fortytwo.signalk.processor.SignalkProcessor; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import com.google.common.collect.ImmutableList; import com.google.common.eventbus.Subscribe; /** * Holds subscription data, wsSessionId, path, period * If a subscription is made via REST before the websocket is started then the wsSocket will hold the sessionId. * This must be swapped for the wsSessionId when the websocket starts. * The subscription will be in an inactive state when submitted by REST if wsSession = sessionId * * @author robert * */ public class Subscription { private static Logger logger = LogManager.getLogger(Subscription.class); String wsSession = null; String path = null; long period = -1; boolean active = true; private long minPeriod; private String format; private String policy; private Pattern pattern = null; private String vesselPath; Set<String> subscribedPaths = new ConcurrentSet<String>(); private String routeId; private String destination; //private String outputType; public Subscription(String wsSession, String path, long period, long minPeriod, String format, String policy) { this.wsSession = wsSession; this.path = SignalkProcessor.sanitizePath(path); pattern = SignalkProcessor.regexPath(this.path); this.period = period; this.minPeriod = minPeriod; this.format = format; this.policy = policy; SignalKModelFactory.getInstance().getEventBus().register(this); for(String p: ImmutableList.copyOf(SignalKModelFactory.getInstance().getKeys())){ if(isSubscribed(p)){ subscribedPaths.add(p); } } } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((path == null) ? 0 : path.hashCode()); result = prime * result + (int) (period ^ (period >>> 32)); result = prime * result + ((wsSession == null) ? 0 : wsSession.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Subscription other = (Subscription) obj; if (path == null) { if (other.path != null) return false; } else if (!path.equals(other.path)) return false; if (period != other.period) return false; if (wsSession == null) { if (other.wsSession != null) return false; } else if (!wsSession.equals(other.wsSession)) return false; return true; } public String getWsSession() { return wsSession; } public void setWsSession(String wsSession) { this.wsSession = wsSession; } public String getPath() { return path; } public void setPath(String path) { this.path = SignalkProcessor.sanitizePath(path); } public long getPeriod() { return period; } public void setPeriod(long period) { this.period = period; } @Override public String toString() { return "Subscription [wsSession=" + wsSession + ", path=" + path + ", period=" + period + ", routeId=" + routeId+ ", format=" + format + ", active=" + active + ", destination=" + destination + "]"; } public boolean isActive() { return active; } public void setActive(boolean active) { this.active = active; } public boolean isSameRoute(Subscription sub) { if (period != sub.period) return false; if (wsSession == null) { if (sub.wsSession != null) return false; } else if (!wsSession.equals(sub.wsSession)) return false; if (format == null) { if (sub.format != null) return false; } else if (!format.equals(sub.format)) return false; if (policy == null) { if (sub.policy != null) return false; } else if (!policy.equals(sub.policy)) return false; return true; } public boolean isSameVessel(Subscription sub) { if (getVesselPath() != null && getVesselPath().equals(sub.getVesselPath())) return true; return false; } /** * Gets the context path, eg vessels.motu, vessel.*, or vessels.2933?? * * @param path * @return */ public String getVesselPath() { if (vesselPath == null) { if (!path.startsWith(vessels)) return null; int pos = path.indexOf(".") + 1; // could be just 'vessels' if (pos < 1) return null; pos = path.indexOf(".", pos); // could be just one .\dot. vessels.123456789 if (pos < 0) return path; vesselPath = path.substring(0, pos); } return vesselPath; } /** * Returns true if this subscription is interested in this path * * @param key * @return */ public boolean isSubscribed(String key) { return pattern.matcher(key).find(); } public long getMinPeriod() { return minPeriod; } public String getFormat() { return format; } public String getPolicy() { return policy; } /** * Returns a list of paths that this subscription is currently providing. * The list is filtered by the key if it is not null or empty in which case a full list is returned, * @param key * @return */ public List<String> getSubscribed(String key) { if(StringUtils.isBlank(key)){ return ImmutableList.copyOf(subscribedPaths); } List<String> paths = new ArrayList<String>(); for (String p : subscribedPaths) { if (p.startsWith(key)) { if(logger.isDebugEnabled())logger.debug("Adding path:" + p); paths.add(p); } } return paths; } /** * Listens for node changes in the server and adds them if they match the subscription * * @param pathEvent */ @Subscribe public void recordEvent(PathEvent pathEvent) { if (pathEvent == null) return; if (pathEvent.getPath() == null) return; if(path.endsWith(dot+source) && path.endsWith(dot+timestamp) && path.contains(dot+source+dot) && path.endsWith(dot+sourceRef)){ return; } if (logger.isDebugEnabled()) logger.debug(this.hashCode() + " received event " + pathEvent.getPath()); if(isSubscribed(pathEvent.getPath())){ subscribedPaths.add(pathEvent.getPath()); } } public void setRouteId(String routeId) { this.routeId=routeId; } public String getRouteId() { return routeId; } public String getDestination() { return destination; } public void setDestination(String destination) { this.destination = destination; } }