package geniusweb.profilesserver.websocket; import java.io.IOException; import java.lang.management.ManagementFactory; import java.net.InetAddress; import java.net.URI; import java.net.URISyntaxException; import java.net.UnknownHostException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.stream.Collectors; import javax.management.MBeanServer; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; import javax.management.Query; import javax.websocket.OnClose; import javax.websocket.OnError; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; import geniusweb.profilesserver.Constants; import geniusweb.profilesserver.ProfilesRepository; import geniusweb.profilesserver.events.ChangeEvent; import tudelft.utilities.listener.Listener; import tudelft.utilities.logging.ReportToLogger; import tudelft.utilities.logging.Reporter; /** * Returns a websocket that communicates the list of currently available domains * and profiles. Every time something changes, a new list of domains and * profiles is sent. For each new websocket the server will create one of this * but they all share one {@link ProfilesRepository}. */ @ServerEndpoint("/websocket/liststream") public class ProfilesListSocket { private final Reporter log; /** following both final but set in {@link #start(Session)} */ private Session session; private Listener changeListener; private static transient String hostport = ""; // cache public ProfilesListSocket() { this(new ReportToLogger("profilesserver")); } public ProfilesListSocket(ReportToLogger reportToLogger) { this.log = reportToLogger; } @OnOpen public void start(Session session) throws IOException { this.session = session; log.log(Level.INFO, "New connection " + session.getRequestURI()); sendupdatedProfiles(); changeListener = new Listener() { @Override public void notifyChange(ChangeEvent data) { try { sendupdatedProfiles(); } catch (IOException e) { e.printStackTrace(); } } }; Profiles.repository.addListener(changeListener); } /** * Send the latest profiles to the client. * * @throws IOException */ private void sendupdatedProfiles() throws IOException { log.log(Level.FINER, "sending updated profiles"); session.getBasicRemote().sendText( Constants.getJackson().writeValueAsString(getDomainsProfiles())); } /** * @return list of domain and profile URIs, as a hashmap. */ private Map> getDomainsProfiles() { Map> allprofiles = new HashMap<>(); for (String domain : Profiles.repository.getDomains()) { List profiles = Profiles.repository.getProfiles(domain).stream() .map(profile -> makeURI(domain, profile.getName())) .collect(Collectors.toList()); allprofiles.put(makeURI(domain, null), profiles); } return allprofiles; } /** * @param domain the domain name. * @param profile the profile name. If null, a ref to the the domain (no * profile) is needed * @return a URI where to get the given profile/domain. */ private URI makeURI(String domain, String profile) { try { return new URI("ws://" + getIpAddressAndPort() + "/" + getServerName() + "/websocket/get/" + domain + (profile != null ? "/" + profile : "")); } catch (MalformedObjectNameException | UnknownHostException | URISyntaxException e) { log.log(Level.SEVERE, "Failed to create profile URI", e); return null; } } /** * * @return name of our service, eg "profilesserver" */ private String getServerName() { // typically something like /profilesserver/websocket/liststream // profilesserver is the name we're looking for return session.getRequestURI().getPath().split("/")[1]; // drop leading // '/' } private String getIpAddressAndPort() throws UnknownHostException, MalformedObjectNameException { synchronized (hostport) { if (hostport.isEmpty()) { MBeanServer beanServer = ManagementFactory .getPlatformMBeanServer(); Set objectNames = beanServer.queryNames( new ObjectName("*:type=Connector,*"), Query.match(Query.attr("protocol"), Query.value("HTTP/1.1"))); String host = InetAddress.getLocalHost().getHostAddress(); String port = objectNames.iterator().next() .getKeyProperty("port"); hostport = host + ":" + port; } return hostport; } } @OnClose public void end() throws IOException { Profiles.repository.removeListener(changeListener); } @OnError public void onError(Throwable t) throws Throwable { t.printStackTrace(); } }