package geniusweb.profilesserver; import java.io.IOException; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import geniusweb.issuevalue.Domain; import geniusweb.profile.Profile; import geniusweb.profilesserver.events.ChangeEvent; import geniusweb.profilesserver.events.DomainChangeEvent; import geniusweb.profilesserver.events.ProfileChangeEvent; import tudelft.utilities.listener.DefaultListenable; /** * Listeners will get a notification call with a {@link ChangeEvent} when a * {@link Domain} or {@link Profile} is changed/removed. *

* Warning: If possible create only 1 instance of this to avoid expensive * duplicate bookkeeping. This class could , but is not made a singleton to * facilitate testing. *

* This object is mutable: the {@link #available} map is updated with the * current state of available domains and profiles. */ public class DefaultProfilesRepository extends DefaultListenable implements ProfilesRepository { /** * all currently available profiles and domains. Invariant for this class: * All {@link Domain}s in this map have a unique name that matches the * directory name and file name on the file system below {@link #dir}. All * profile values in the map their {@link Profile#getDomain()} match with * the domain key in the map. */ protected final Map> available = new ConcurrentHashMap<>(); @Override public synchronized Domain getDomain(String name) { if (name == null) { throw new IllegalArgumentException("domain = null"); } Optional optional = available.keySet().stream() .filter(domain -> name.equals(domain.getName())).findFirst(); return optional.isPresent() ? optional.get() : null; } @Override public synchronized Profile getProfile(String domainprofilename) { if (domainprofilename == null) { throw new IllegalArgumentException("profilename=null"); } String[] values = domainprofilename.split("/", 2); if (values.length != 2) { throw new IllegalArgumentException( "profile name must be / but got " + domainprofilename); } // check if domain exists. Domain domain = getDomain(values[0]); if (domain == null) { return null; } String profilename = values[1]; Optional profile = available.get(domain).stream() .filter(prof -> profilename.equals(prof.getName())).findFirst(); return profile.isPresent() ? profile.get() : null; } @Override public synchronized List getProfiles(String domainname) { Domain domain = getDomain(domainname); return domain == null ? Collections.EMPTY_LIST : Collections.unmodifiableList(available.get(domain)); } @Override public synchronized List getDomains() { return available.keySet().stream().map(domain -> domain.getName()) .collect(Collectors.toList()); } @Override public synchronized void putProfile(Profile profile) throws IOException { if (!available.containsKey(profile.getDomain())) { throw new IOException("profile's domain does not exist"); } add(profile); } /**************** support for updating ***************/ /** * Add or replace domain. For internal use so little checking of arguments. * If the domain already exists and is the same, we exit immediately. If * such a domain does not yet exist, or a domain with the same name already * exists but that domain is different, the old domain is removed (and all * associated profiles). The new domain is inserted without any known * profiles. * * @param domain the new domain. */ protected void add(Domain domain) { synchronized (this) { if (domain == null) { throw new IllegalArgumentException("domain=null"); } if (available.containsKey(domain)) return; Optional existingdom = available.keySet().stream() .filter(dom -> dom.getName().equals(domain.getName())) .findFirst(); if (existingdom.isPresent()) { remove(existingdom.get()); } available.put(domain, new LinkedList<>()); } notifyListeners(new DomainChangeEvent(null, domain)); } /** * Removes domain from the known domains list, plus all profiles associated * with the domain. For internal use so little checking of arguments. * * @param domain the domain to remove. If null, nothing happens. */ protected void remove(Domain domain) { if (domain == null) return; List removedprofiles; synchronized (this) { removedprofiles = available.get(domain); available.remove(domain); } for (Profile prof : removedprofiles) { notifyListeners(new ProfileChangeEvent(prof, null)); } notifyListeners(new DomainChangeEvent(domain, null)); } /** * Adds profile. Internal use only. Domain must exist already. If a profile * with same name already exists, that one is replaced with the given * profile. * * @param profile the profile to add. Ignored if profile=null or if profile * already known. Profile MUST contain a registered domain. */ protected void add(Profile profile) { Profile oldprofile = null; synchronized (this) { if (profile == null) return; List profiles = available.get(profile.getDomain()); if (profiles == null) { throw new IllegalArgumentException("Profile " + profile.getName() + " uses unknown domain " + profile.getDomain()); } Optional knownprofile = profiles.stream() .filter(prof -> profile.getName().equals(prof.getName())) .findFirst(); if (knownprofile.isPresent()) { oldprofile = knownprofile.get(); if (profile.equals(oldprofile)) return; profiles.remove(oldprofile); } profiles.add(profile); } notifyListeners(new ProfileChangeEvent(oldprofile, profile)); } /** * Removes profile. Internal use only. * * @param profile to be removed {@link Profile} */ protected void remove(Profile profile) { synchronized (this) { if (profile == null) { throw new IllegalArgumentException("profile=null"); } List profilelist = available.get(profile.getDomain()); if (profilelist == null) { throw new IllegalArgumentException("Profile " + profile.getName() + " uses unknown domain " + profile.getDomain()); } profilelist.remove(profile); } notifyListeners(new ProfileChangeEvent(profile, null)); } /** * * @param name the name to check * @return true iff name is simple name. False if not (also if name=null) */ protected boolean isSimpleName(String name) { if (name == null) return false; return name.matches("[a-zA-Z0-9]+"); } }