package geniusweb.profilesserver.websocket; import java.io.IOException; import java.math.BigInteger; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.stream.Collectors; import javax.websocket.OnClose; import javax.websocket.OnError; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import geniusweb.bidspace.AllBidsList; import geniusweb.bidspace.BidsWithUtility; import geniusweb.issuevalue.Bid; import geniusweb.profile.DefaultPartialOrdering; import geniusweb.profile.PartialOrdering; import geniusweb.profile.Profile; import geniusweb.profile.utilityspace.LinearAdditive; import geniusweb.profilesserver.Constants; import geniusweb.profilesserver.ProfilesRepository; import geniusweb.profilesserver.events.ChangeEvent; import tudelft.utilities.immutablelist.FixedList; import tudelft.utilities.immutablelist.ImmutableList; import tudelft.utilities.listener.Listener; /** * 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}. * *

* Query strings: the websocket allows query strings. Allowed: * * * * * * * * * * *
Query strings for GetprofileSocket
query namedescription
partial=Na query key that is assigned a natural number N (0 or larger). If set, * the profile is converted into a Partial Profile with the given number of * points. If N>0, the maximum utility bid is included .If N>1, the * minimum utility bid is included. The other bids are picked at random. This * filtering mechanism only works if the profile is {@link LinearAdditive} * space. The reservation bid is passed through unmodified.
*/ @ServerEndpoint("/websocket/get/{domain}/{profile}") public class GetProfileSocket { private Profile prof = null; // the latest that we sent to client. // should all be final, except that we can only set them when start is // called... private String profilename; private Listener changeListener; private Session session; private Map params = Collections.emptyMap(); @OnOpen public void start(Session session, @PathParam("domain") final String domain, @PathParam("profile") final String profile) throws IOException { this.session = session; this.profilename = domain + "/" + profile; if (session.getQueryString() != null) { List paramstrings = Arrays .asList(session.getQueryString().split("&")); params = paramstrings.stream().map(str -> str.split("=")).collect( Collectors.toMap(vals -> vals[0], vals -> vals[1])); } changeListener = new Listener() { @Override public void notifyChange(ChangeEvent data) { sendupdatedProfile(); } }; sendupdatedProfile(); Profiles.repository.addListener(changeListener); } private void sendupdatedProfile() { Profile newprof = Profiles.repository.getProfile(profilename); // notice, may be null if profile does not exist/was removed. if (newprof == null ? prof != null : !newprof.equals(prof)) { prof = newprof; try { session.getBasicRemote().sendText( Constants.getJackson().writeValueAsString(filter(prof))); } catch (Exception e) { e.printStackTrace(); } } } /** * * @param prof1 * @return a filtered profile. If "partial" is not set, this returns the * given profile unmodified. If partial is set, a * DefaultPartialOrdering with the requested number of bids are * selected */ private Profile filter(Profile prof1) { String partial = params.get("partial"); if (partial == null) { return prof1; } if (!(prof1 instanceof PartialOrdering)) throw new IllegalArgumentException( "profile must be partialordering but got " + prof); PartialOrdering profile = (PartialOrdering) prof1; final int numbids = Integer.parseInt(partial); if (numbids < 0) throw new IllegalArgumentException("parameter partial must be >=0"); ImmutableList allbids; if (profile instanceof DefaultPartialOrdering) allbids = new FixedList( ((DefaultPartialOrdering) profile).getBids()); else allbids = new AllBidsList(prof.getDomain()); if (BigInteger.valueOf(numbids).compareTo(allbids.size()) > 0) throw new IllegalArgumentException("Request for " + numbids + " exceeds number of bids in the space " + allbids.size()); BidsWithUtility info = new BidsWithUtility((LinearAdditive) prof); Set selected = new HashSet<>(); if (numbids > 0) { selected.add(info.getExtremeBid(true)); } if (numbids > 1) { selected.add(info.getExtremeBid(false)); } Random random = new Random(); for (int nr = 2; nr < numbids; nr++) { int attempt = 0; Bid bid; do { long i = random.nextInt(allbids.size().intValue()); bid = allbids.get(BigInteger.valueOf(i)); } while (attempt++ < 10 && selected.contains(bid)); selected.add(bid); } // put comparison info for these bids in the map Map> isBetterMap = new HashMap<>(); for (Bid bid : selected) { Set worse = selected.stream().filter( otherbid -> profile.isPreferredOrEqual(bid, otherbid) && !profile.isPreferredOrEqual(otherbid, bid)) .collect(Collectors.toSet()); isBetterMap.put(bid, worse); } return new DefaultPartialOrdering( prof.getName() + "-partial-" + numbids, prof.getDomain(), prof.getReservationBid(), isBetterMap); } @OnClose public void end() throws IOException { Profiles.repository.removeListener(changeListener); } @OnError public void onError(Throwable t) throws Throwable { t.printStackTrace(); } }