Index: src/main/java/geniusweb/profilesserver/AutoUpdatingProfilesRepository.java
===================================================================
--- src/main/java/geniusweb/profilesserver/AutoUpdatingProfilesRepository.java	(revision 16)
+++ src/main/java/geniusweb/profilesserver/AutoUpdatingProfilesRepository.java	(revision 16)
@@ -0,0 +1,414 @@
+package geniusweb.profilesserver;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.net.URISyntaxException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.logging.Level;
+
+import geniusweb.issuevalue.Domain;
+import geniusweb.profile.Profile;
+import tudelft.utilities.files.FileInfo;
+import tudelft.utilities.files.FileWatcher;
+import tudelft.utilities.immutablelist.Tuple;
+import tudelft.utilities.listener.Listenable;
+import tudelft.utilities.listener.Listener;
+import tudelft.utilities.logging.Reporter;
+
+/**
+ * As {@link DefaultProfilesRepository}, but auto synchronizes the contents of
+ * the repository according to changes in the filesystem. Domain and profile
+ * contents should match their directoryname/filename. A directory can only
+ * contain files for the domain with the same name as the directoryname.
+ * 
+ * This repository tries to use the value in the environment variable
+ * "PROFILES_ROOT" as base dir for the available profiles. If it does not exist,
+ * it assumes "repo" in the current working directory. It is assumed ot contain
+ * a directory for each domain, with the name of the directory matching the
+ * {@link Domain#getName()}. We recomend to set an absolute path to
+ * PROFILES_ROOT to avoid problems if something changes the working directory.
+ * <p>
+ * Thread safe. It is heavily synchronized to ensure thread safety. It does not
+ * manipulate the filesystem to avoid collisions between trackers and updaters.
+ * <p>
+ * Tech note: This is not a singleton because we need to override functions for
+ * testing purposes.
+ */
+public class AutoUpdatingProfilesRepository extends DefaultProfilesRepository {
+
+	private static final String PROFILES_ROOTDIR = "PROFILES_ROOTDIR";
+	private static final String DOMAINSREPO = "domainsrepo";
+	private static final String JSON = ".json";
+	private final Listenable<Tuple<FileInfo, FileInfo>> watcher;
+	private Path rootdir;
+	private final Reporter log;
+
+	public AutoUpdatingProfilesRepository(Reporter logger) {
+		this.log = logger;
+		this.rootdir = getRootDir();
+
+		watcher = getFileWatcher(rootdir.toFile());
+		watcher.addListener(new Listener<Tuple<FileInfo, FileInfo>>() {
+			@Override
+			public void notifyChange(Tuple<FileInfo, FileInfo> info) {
+				update(info);
+			}
+		});
+		// manually trigger the first event.
+		update(new Tuple<FileInfo, FileInfo>(null,
+				new FileInfo(rootdir.toFile(), 2)));
+	}
+
+	@Override
+	public synchronized void putProfile(Profile profile) throws IOException {
+		if (!available.containsKey(profile.getDomain())) {
+			throw new IOException("profile's domain does not exist");
+		}
+		String domainname = profile.getDomain().getName();
+		String profilename = profile.getName();
+		if (!profilename.matches("[a-zA-Z0-9-]+"))
+			throw new IOException("illegal profile name " + profilename
+					+ ", only letters, numbers and - allowed");
+		File file = Paths
+				.get(rootdir.toString(), domainname, profilename + JSON)
+				.toFile();
+		if (file.exists()) {
+			String msg = fileCheck(file, false, true);
+			if (msg != null)
+				throw new IOException(msg);
+		}
+
+		// CHECK utf-8 compatibility? Do we need that anyway?
+		try (Writer out = new FileWriter(file, false)) {
+			out.write(Jackson.instance().writeValueAsString(profile));
+		}
+		// trigger the update immediately, avoid wait for FileChecker.
+		super.add(profile);
+
+	}
+
+	/**
+	 * Called when some file that may be relevant has changed. Synchronized to
+	 * avoid weird states if filesystem changes rapidly.
+	 */
+	private synchronized void update(Tuple<FileInfo, FileInfo> info) {
+		log.log(Level.INFO, "change: " + info);
+		File oldfile = info.get1() == null ? null : info.get1().getFile();
+		File newfile = info.get2() == null ? null : info.get2().getFile();
+
+		File file = oldfile != null ? oldfile : newfile;
+		Path relpath = file.toPath().relativize(rootdir);
+		// workaround: root gives "" with depth 1 instead of 0.
+		int depth = relpath.toString().isEmpty() ? 0 : relpath.getNameCount();
+
+		switch (depth) {
+		case 0:
+			// root changed! Update ALL directories we saw and see.
+			reset();
+			break;
+		case 1: // change of directory name?
+			if (newfile != null) {
+				if (newfile.isDirectory()) {
+					updateDomainDirectory(newfile);
+				} else {
+					log.log(Level.INFO, "expected directory but found file: "
+							+ newfile.getAbsolutePath());
+				}
+			} else {
+				// new file is null. Removed
+				remove(getDomain(oldfile.getName()));
+			}
+			break;
+		case 2: // A file changed, added or removed inside profile directory
+			if (newfile != null) {
+				// file was added.
+				if (newfile.isDirectory()) {
+					log.log(Level.INFO,
+							"expected file but found directory: " + file);
+				}
+				updateDomainDirectory(newfile.getParentFile());
+			} else {
+				// Some file was removed. Check entire directory again
+				updateDomainDirectory(oldfile.getParentFile());
+			}
+			break;
+		default:
+			log.log(Level.WARNING, "Ignoring unexpected event at " + info);
+		}
+
+	}
+
+	/**
+	 * Remove all domains and reload them all from scratch
+	 */
+	private void reset() {
+		// first remove all old stuff. Copy domains as we're going to modify.
+		for (Domain domain : new LinkedList<>(available.keySet())) {
+			remove(domain);
+		}
+		if (!basicFileCheck(rootdir.toFile(), true))
+			return;
+		for (File dir : rootdir.toFile().listFiles()) {
+			if (!dir.isDirectory())
+				continue;
+			updateDomainDirectory(dir);
+		}
+	}
+
+	/**
+	 * A domain directory was added or updated (not deleted). Scan the contents
+	 * and compare/update the repo.
+	 * 
+	 * @param dir the directory that could contain domain specific files -
+	 *            domain description and profiles.
+	 */
+	private void updateDomainDirectory(File dir) {
+		if (!basicFileCheck(dir, true)) {
+			remove(getDomain(dir.getName()));
+			return;
+		}
+		// if we get here, dir is indeed a directory and we can read contents.
+		File domainfile = domainFile(dir.getName());
+		Domain newdomain = deserializeDomain(domainfile);
+		Domain olddomain = getDomain(dir.getName());
+		if (olddomain != null && !olddomain.equals(newdomain)) {
+			remove(olddomain); // also removes old profiles.
+		}
+
+		if (newdomain == null) {
+			return;
+		}
+		add(newdomain);
+		updateProfiles(newdomain);
+	}
+
+	/**
+	 * Update all profiles in given directory
+	 * 
+	 * @param dir
+	 */
+	private void updateProfiles(Domain domain) {
+		final Path domaindir = rootdir.resolve(domain.getName());
+		final File domainfile = domainFile(domain.getName());
+		final List<Profile> newprofiles = new LinkedList<>();
+		for (File file : domaindir.toFile().listFiles()) {
+			if (file.equals(domainfile))
+				continue;
+			Profile newprofile = deserializeProfile(file);
+			if (newprofile == null)
+				continue;
+			if (!newprofile.getDomain().equals(domain)) {
+				log.log(Level.WARNING,
+						"Profile " + file + " contains incorrect domain "
+								+ newprofile.getDomain());
+				continue;
+			}
+			if (!newprofile.getName()
+					.equals(withoutExtension(file.getName()))) {
+				log.log(Level.WARNING, "Profile " + file
+						+ " contains incorrect name " + newprofile.getName());
+				continue;
+			}
+			newprofiles.add(newprofile);
+		}
+		// remove profiles not on the filesystem anymore/have errors
+		for (Profile profile : getProfiles(domain.getName())) {
+			if (!newprofiles.contains(profile)) {
+				remove(profile);
+			}
+		}
+		// (re)add currently existing profiles.
+		for (Profile profile : newprofiles) {
+			add(profile);
+		}
+	}
+
+	/**
+	 * Reads profile and also checks that this profile is in the correct
+	 * directory (the domain should match the registered domain for this
+	 * directory). The domain for this directory MUST have been registered.
+	 * 
+	 * @param profilefiile the profile file to read. Should contain a
+	 *                     {@link Profile}.
+	 * @return profile, or null if failed to read. If failed, we log the
+	 *         details.
+	 */
+
+	private Profile deserializeProfile(File profilefile) {
+		if (!basicFileCheck(profilefile, false))
+			return null;
+		Profile profile = null;
+		try {
+			profile = Jackson.instance().readValue(profilefile, Profile.class);
+		} catch (IOException e) {
+			log.log(Level.WARNING, "File " + profilefile
+					+ " does not appear to contain a profile", e);
+			return null;
+		}
+		Domain expectedDomain = getDomain(
+				profilefile.getParentFile().getName());
+		if (!profile.getDomain().equals(expectedDomain)) {
+			log.log(Level.WARNING, "Profile has incorrect domain: expected "
+					+ expectedDomain + " but found " + profile.getDomain());
+			return null;
+
+		}
+		return profile;
+	}
+
+	/**
+	 * Try to deserialize a domain file in given directory.
+	 * 
+	 * @param dir the directory where we expect a domain file
+	 * @return deserialized domain file, or null if deserialization fails (eg no
+	 *         such file, or wrong contents, domain has name not matching the
+	 *         actual file). If fails, a warning is logged with details.
+	 * 
+	 * 
+	 */
+	private Domain deserializeDomain(File domainfile) {
+		if (!basicFileCheck(domainfile, false))
+			return null;
+		Domain domain = null;
+		String expectedName = withoutExtension(domainfile.getName());
+		try {
+			domain = Jackson.instance().readValue(domainfile, Domain.class);
+		} catch (IOException e) {
+			log.log(Level.WARNING, "File " + domainfile
+					+ " does not appear to contain a Domain", e);
+			return null;
+		}
+		if (!expectedName.equals(domain.getName())) {
+			log.log(Level.WARNING,
+					"domain file " + domainfile + " contains name:'"
+							+ domain.getName()
+							+ "' which does not match the required name "
+							+ expectedName);
+			return null;
+		}
+		return domain;
+
+	}
+
+	/**
+	 * Silent check and report basic problems with a file. Silent in the sense
+	 * that errors are logged but cause no exception.
+	 * 
+	 * @param file              the file to check
+	 * @param shouldbeDirectory true if the file should be a directory
+	 * @return false iff basic checks on the file fails.
+	 * 
+	 */
+	private boolean basicFileCheck(File file, boolean shouldbeDirectory) {
+		String msg = fileCheck(file, shouldbeDirectory, false);
+		if (msg != null) {
+			log.log(Level.WARNING, msg);
+			return false;
+		}
+		return true;
+	}
+
+	/**
+	 * 
+	 * @param file              the file to check
+	 * @param shouldbeDirectory true if expected a directory
+	 * @param shouldbeWritable  true if expected to be writable
+	 * @return null if file is there, is file/directory, allows reading and
+	 *         allows writing (if specified).
+	 */
+	private String fileCheck(File file, boolean shouldbeDirectory,
+			boolean shouldbeWritable) {
+		if (!file.exists()) {
+			return file + " is missing";
+		}
+		if (!file.canRead()) {
+			return "directory " + file + "does not allow reading ";
+		}
+		if (shouldbeDirectory) {
+			if (!file.isDirectory()) {
+				return file + " is not a directory";
+			}
+		} else {
+			if (!file.isFile()) {
+				return file + " is not a file";
+			}
+		}
+		if (shouldbeWritable && !file.canWrite()) {
+			return file + "is read-only";
+		}
+		return null;
+
+	}
+
+	private String withoutExtension(String name) {
+		if (!name.contains("."))
+			return name;
+		return name.substring(0, name.indexOf("."));
+	}
+
+	/**
+	 * @param directory a directory that might contain a domain file.
+	 * @return the file that contains the domain description in the given
+	 *         directory.
+	 * 
+	 */
+	private File domainFile(String domainname) {
+		return Paths.get(rootdir.toString(), domainname, domainname + JSON)
+				.toFile();
+	}
+
+	/**
+	 * Factory method that gives a watcher for file changes. See
+	 * {@link FileWatcher}.
+	 * 
+	 * @param file
+	 * @return a class that watches for file changes below the given file.
+	 */
+	protected Listenable<Tuple<FileInfo, FileInfo>> getFileWatcher(File file) {
+		return new FileWatcher(file, 3000, 2);
+	}
+
+	/**
+	 * Search from location of this class upwards till we find a directory
+	 * called "domainsrepo".
+	 * 
+	 * @return the root dir of the repo. This is the 'database' where all known
+	 *         profiles are stored.
+	 * @throws URISyntaxException
+	 */
+	protected Path getRootDir() {
+		// If you run this normal from tomcat we are somewhere inside
+		// projectroot/WEB_INF/classes/....
+		// Search back upwards to projectroot.
+		Path dir;
+		String path = System.getenv(PROFILES_ROOTDIR);
+		if (path != null) {
+			dir = Paths.get(path);
+			if (!dir.toFile().exists())
+				throw new RuntimeException("Hard set path PROFILES_ROOTDIR='"
+						+ PROFILES_ROOTDIR + "' does not exist ");
+			return dir;
+		}
+		try {
+			dir = Paths.get(getClass().getProtectionDomain().getCodeSource()
+					.getLocation().toURI()).getParent();
+		} catch (URISyntaxException e) {
+			throw new RuntimeException("Problem with path URI", e);
+		}
+		System.out.println("searching from " + dir);
+		while (dir.getNameCount() > 1) {
+			Path repo = dir.resolve(DOMAINSREPO);
+			if (repo.toFile().exists())
+				return repo;
+			System.out.println("Directory did not contain repo:" + dir);
+			dir = dir.getParent();
+		}
+		throw new RuntimeException("Path to " + DOMAINSREPO + " was not found");
+	}
+
+}
Index: src/main/java/geniusweb/profilesserver/DefaultProfilesRepository.java
===================================================================
--- src/main/java/geniusweb/profilesserver/DefaultProfilesRepository.java	(revision 16)
+++ src/main/java/geniusweb/profilesserver/DefaultProfilesRepository.java	(revision 16)
@@ -0,0 +1,214 @@
+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.
+ * <p>
+ * 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.
+ * <p>
+ * This object is mutable: the {@link #available} map is updated with the
+ * current state of available domains and profiles.
+ */
+public class DefaultProfilesRepository extends DefaultListenable<ChangeEvent>
+		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<Domain, List<Profile>> available = new ConcurrentHashMap<>();
+
+	@Override
+	public synchronized Domain getDomain(String name) {
+		if (name == null) {
+			throw new IllegalArgumentException("domain = null");
+		}
+		Optional<Domain> 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 <domainname>/<profilename> but got "
+							+ domainprofilename);
+		}
+		// check if domain exists.
+		Domain domain = getDomain(values[0]);
+		if (domain == null) {
+			return null;
+		}
+		String profilename = values[1];
+		Optional<Profile> profile = available.get(domain).stream()
+				.filter(prof -> profilename.equals(prof.getName())).findFirst();
+		return profile.isPresent() ? profile.get() : null;
+	}
+
+	@Override
+	public synchronized List<Profile> getProfiles(String domainname) {
+		Domain domain = getDomain(domainname);
+		return domain == null ? Collections.EMPTY_LIST
+				: Collections.unmodifiableList(available.get(domain));
+
+	}
+
+	@Override
+	public synchronized List<String> 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<Domain> 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<Profile> 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<Profile> profiles = available.get(profile.getDomain());
+			if (profiles == null) {
+				throw new IllegalArgumentException("Profile "
+						+ profile.getName() + "  uses unknown domain "
+						+ profile.getDomain());
+			}
+			Optional<Profile> 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<Profile> 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]+");
+	}
+
+}
Index: src/main/java/geniusweb/profilesserver/ProfilesRepository.java
===================================================================
--- src/main/java/geniusweb/profilesserver/ProfilesRepository.java	(revision 16)
+++ src/main/java/geniusweb/profilesserver/ProfilesRepository.java	(revision 16)
@@ -0,0 +1,52 @@
+package geniusweb.profilesserver;
+
+import java.io.IOException;
+import java.util.List;
+
+import geniusweb.issuevalue.Domain;
+import geniusweb.profile.Profile;
+import geniusweb.profilesserver.events.ChangeEvent;
+import tudelft.utilities.listener.Listenable;
+
+/**
+ * Profiles repository interface defining the access methods to the server.
+ */
+public interface ProfilesRepository extends Listenable<ChangeEvent> {
+	/**
+	 * 
+	 * @param domainname a simple name (no extensions or so) as specified in the
+	 *                   domain (so no full path or special characters).
+	 * @return domain with the given name or null if no such domain exists.
+	 */
+	Domain getDomain(String domainname);
+
+	/**
+	 * @param simple profilename of the form "domainname/profileX" (no
+	 *               extendsions).
+	 * @return profile with the given name and the given domain, or null if no
+	 *         such domain or profile exists.
+	 * 
+	 */
+	Profile getProfile(String profilename);
+
+	/**
+	 * @return a list of all domain names
+	 */
+	List<String> getDomains();
+
+	/**
+	 * @param domainname simple name of the domain.
+	 * @return all profiles currently known for given domain
+	 */
+	List<Profile> getProfiles(String domainname);
+
+	/**
+	 * Permanently (over)write a profile to the repository (not just in memory).
+	 * 
+	 * @param profile the profile to be written. The domain file must exist and
+	 *                match.
+	 * @throws IOException if the profile does not meet the requirements.
+	 */
+	void putProfile(Profile profile) throws IOException;
+
+}
Index: src/main/java/geniusweb/profilesserver/websocket/GetProfileSocket.java
===================================================================
--- src/main/java/geniusweb/profilesserver/websocket/GetProfileSocket.java	(revision 13)
+++ src/main/java/geniusweb/profilesserver/websocket/GetProfileSocket.java	(revision 16)
@@ -28,5 +28,5 @@
 import geniusweb.profile.utilityspace.LinearAdditive;
 import geniusweb.profilesserver.Jackson;
-import geniusweb.profilesserver.ProfilesFactory;
+import geniusweb.profilesserver.ProfilesRepository;
 import geniusweb.profilesserver.events.ChangeEvent;
 import tudelft.utilities.immutablelist.FixedList;
@@ -38,5 +38,5 @@
  * 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 ProfilesFactory}.
+ * but they all share one {@link ProfilesRepository}.
  * 
  * <p>
@@ -80,9 +80,9 @@
 		};
 		sendupdatedProfile();
-		Profiles.factory.addListener(changeListener);
+		Profiles.repository.addListener(changeListener);
 	}
 
 	private void sendupdatedProfile() {
-		Profile newprof = Profiles.factory.getProfile(profilename);
+		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)) {
@@ -164,5 +164,5 @@
 	@OnClose
 	public void end() throws IOException {
-		Profiles.factory.removeListener(changeListener);
+		Profiles.repository.removeListener(changeListener);
 	}
 
Index: src/main/java/geniusweb/profilesserver/websocket/Profiles.java
===================================================================
--- src/main/java/geniusweb/profilesserver/websocket/Profiles.java	(revision 13)
+++ src/main/java/geniusweb/profilesserver/websocket/Profiles.java	(revision 16)
@@ -1,13 +1,14 @@
 package geniusweb.profilesserver.websocket;
 
-import geniusweb.profilesserver.AutoUpdatingProfilesFactory;
-import geniusweb.profilesserver.ProfilesFactory;
+import geniusweb.profilesserver.AutoUpdatingProfilesRepository;
+import geniusweb.profilesserver.ProfilesRepository;
 import tudelft.utilities.logging.ReportToLogger;
 
 /**
- * Just a shared copy of the profiles factory, for use with all websockets.
+ * Just a shared copy of the profiles repository, for use with all websockets.
  * 
  */
 class Profiles {
-	public static final ProfilesFactory factory = new AutoUpdatingProfilesFactory(new ReportToLogger("profilesserver"));
+	public static final ProfilesRepository repository = new AutoUpdatingProfilesRepository(
+			new ReportToLogger("profilesserver"));
 }
Index: src/main/java/geniusweb/profilesserver/websocket/ProfilesListSocket.java
===================================================================
--- src/main/java/geniusweb/profilesserver/websocket/ProfilesListSocket.java	(revision 13)
+++ src/main/java/geniusweb/profilesserver/websocket/ProfilesListSocket.java	(revision 16)
@@ -25,5 +25,5 @@
 
 import geniusweb.profilesserver.Jackson;
-import geniusweb.profilesserver.ProfilesFactory;
+import geniusweb.profilesserver.ProfilesRepository;
 import geniusweb.profilesserver.events.ChangeEvent;
 import tudelft.utilities.listener.Listener;
@@ -35,5 +35,5 @@
  * 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 ProfilesFactory}.
+ * but they all share one {@link ProfilesRepository}.
  */
 @ServerEndpoint("/websocket/liststream")
@@ -71,5 +71,5 @@
 			}
 		};
-		Profiles.factory.addListener(changeListener);
+		Profiles.repository.addListener(changeListener);
 	}
 
@@ -90,6 +90,6 @@
 	private Map<URI, List<URI>> getDomainsProfiles() {
 		Map<URI, List<URI>> allprofiles = new HashMap<>();
-		for (String domain : Profiles.factory.getDomains()) {
-			List<URI> profiles = Profiles.factory.getProfiles(domain).stream()
+		for (String domain : Profiles.repository.getDomains()) {
+			List<URI> profiles = Profiles.repository.getProfiles(domain).stream()
 					.map(profile -> makeURI(domain, profile.getName()))
 					.collect(Collectors.toList());
@@ -153,5 +153,5 @@
 	@OnClose
 	public void end() throws IOException {
-		Profiles.factory.removeListener(changeListener);
+		Profiles.repository.removeListener(changeListener);
 	}
 
