private void updateFavouriteNodes(
      String userName, Type type, Map<PersonFavouriteKey, PersonFavourite> favouriteNodes) {
    PrefKeys prefKeys = getPrefKeys(type);

    Map<String, Serializable> preferences = new HashMap<String, Serializable>(1);

    StringBuilder values = new StringBuilder();
    for (PersonFavourite node : favouriteNodes.values()) {
      NodeRef nodeRef = node.getNodeRef();

      values.append(nodeRef.toString());
      values.append(",");

      // ISO8601 string format: PreferenceService works with strings only for dates it seems
      StringBuilder sb = new StringBuilder(prefKeys.getAlfrescoPrefKey());
      sb.append(nodeRef.toString());
      sb.append(".createdAt");
      String createdAtKey = sb.toString();
      Date createdAt = node.getCreatedAt();
      if (createdAt != null) {
        String createdAtStr = ISO8601DateFormat.format(createdAt);
        preferences.put(createdAtKey, createdAtStr);
      }
    }

    if (values.length() > 1) {
      values.delete(values.length() - 1, values.length());
    }

    preferences.put(prefKeys.getSharePrefKey(), values.toString());
    preferenceService.setPreferences(userName, preferences);
  }
  private String siteFavouritedKey(SiteInfo siteInfo) {
    PrefKeys prefKeys = getPrefKeys(Type.SITE);

    StringBuilder sitePrefKeyBuilder = new StringBuilder(prefKeys.getSharePrefKey());
    sitePrefKeyBuilder.append(siteInfo.getShortName());
    String sitePrefKey = sitePrefKeyBuilder.toString();

    String favouritedKey = sitePrefKey;
    return favouritedKey;
  }
  private String siteCreatedAtKey(SiteInfo siteInfo) {
    PrefKeys prefKeys = getPrefKeys(Type.SITE);

    StringBuilder sitePrefKeyBuilder = new StringBuilder(prefKeys.getAlfrescoPrefKey());
    sitePrefKeyBuilder.append(siteInfo.getShortName());
    String sitePrefKey = sitePrefKeyBuilder.toString();

    StringBuilder createdAtKeyBuilder = new StringBuilder(sitePrefKey);
    createdAtKeyBuilder.append(".createdAt");
    String createdAtKey = createdAtKeyBuilder.toString();
    return createdAtKey;
  }
  private Map<PersonFavouriteKey, PersonFavourite> getFavouriteNodes(String userName, Type type) {
    PrefKeys prefKeys = getPrefKeys(type);
    Map<PersonFavouriteKey, PersonFavourite> favouriteNodes = Collections.emptyMap();
    Map<String, Serializable> prefs =
        preferenceService.getPreferences(userName, prefKeys.getSharePrefKey());
    String nodes = (String) prefs.get(prefKeys.getSharePrefKey());
    if (nodes != null) {
      favouriteNodes = extractFavouriteNodes(userName, type, nodes);
    } else {
      favouriteNodes = new HashMap<PersonFavouriteKey, PersonFavourite>();
    }

    return favouriteNodes;
  }
  private PersonFavourite getFavouriteDocumentOrFolder(
      String userName, Type type, NodeRef nodeRef) {
    PersonFavourite favourite = null;

    PrefKeys prefKeys = getPrefKeys(type);
    Map<String, Serializable> preferences =
        preferenceService.getPreferences(userName, prefKeys.getSharePrefKey());
    String nodes = (String) preferences.get(prefKeys.getSharePrefKey());
    if (nodes != null) {
      Map<PersonFavouriteKey, PersonFavourite> favouriteNodes =
          extractFavouriteNodes(userName, type, nodes);
      String title = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_TITLE);
      PersonFavouriteKey key = new PersonFavouriteKey(userName, title, type, nodeRef);
      favourite = favouriteNodes.get(key);
    }
    return favourite;
  }
  /*
   * Extract favourite nodes of the given type from the comma-separated list in "nodes".
   */
  private Map<PersonFavouriteKey, PersonFavourite> extractFavouriteNodes(
      String userName, Type type, String nodes) {
    PrefKeys prefKeys = getPrefKeys(type);
    Map<PersonFavouriteKey, PersonFavourite> favouriteNodes =
        new HashMap<PersonFavouriteKey, PersonFavourite>();

    StringTokenizer st = new StringTokenizer(nodes, ",");
    while (st.hasMoreTokens()) {
      String nodeRefStr = st.nextToken();
      nodeRefStr = nodeRefStr.trim();
      if (!NodeRef.isNodeRef((String) nodeRefStr)) {
        continue;
      }

      NodeRef nodeRef = new NodeRef((String) nodeRefStr);

      if (!nodeService.exists(nodeRef)) {
        continue;
      }

      if (permissionService.hasPermission(nodeRef, PermissionService.READ_PROPERTIES)
          == AccessStatus.DENIED) {
        continue;
      }

      // get createdAt for this favourited node
      // use ISO8601
      StringBuilder builder = new StringBuilder(prefKeys.getAlfrescoPrefKey());
      builder.append(nodeRef.toString());
      builder.append(".createdAt");
      String prefKey = builder.toString();
      String createdAtStr = (String) preferenceService.getPreference(userName, prefKey);
      Date createdAt = (createdAtStr != null ? ISO8601DateFormat.parse(createdAtStr) : null);

      String name = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_NAME);

      PersonFavourite personFavourite =
          new PersonFavourite(userName, nodeRef, type, name, createdAt);
      PersonFavouriteKey key = personFavourite.getKey();
      favouriteNodes.put(key, personFavourite);
    }

    return favouriteNodes;
  }
  private boolean removeFavouriteSite(String userName, NodeRef nodeRef) {
    PrefKeys prefKeys = getPrefKeys(Type.SITE);
    boolean exists = false;

    SiteInfo siteInfo = siteService.getSite(nodeRef);
    if (siteInfo != null) {
      StringBuilder sitePrefKeyBuilder = new StringBuilder(prefKeys.getSharePrefKey());
      sitePrefKeyBuilder.append(siteInfo.getShortName());
      String sitePrefKey = sitePrefKeyBuilder.toString();

      String siteFavouritedKey = siteFavouritedKey(siteInfo);

      exists = preferenceService.getPreference(userName, siteFavouritedKey) != null;
      preferenceService.clearPreferences(userName, sitePrefKey);
    } else {
      throw new IllegalArgumentException("NodeRef " + nodeRef + " is not a site");
    }

    return exists;
  }
  private void extractFavouriteSite(
      String userName,
      Type type,
      Map<PersonFavouriteKey, PersonFavourite> sortedFavouriteNodes,
      Map<String, Serializable> preferences,
      String key) {
    // preference value indicates whether the site has been favourited
    Serializable pref = preferences.get(key);
    Boolean isFavourite = (Boolean) pref;
    if (isFavourite) {
      PrefKeys sitePrefKeys = getPrefKeys(Type.SITE);
      int length = sitePrefKeys.getSharePrefKey().length();
      String siteId = key.substring(length);

      try {
        SiteInfo siteInfo = siteService.getSite(siteId);
        if (siteInfo != null) {
          StringBuilder builder = new StringBuilder(sitePrefKeys.getAlfrescoPrefKey());
          builder.append(siteId);
          builder.append(".createdAt");
          String createdAtPrefKey = builder.toString();
          String createdAtStr = (String) preferences.get(createdAtPrefKey);
          Date createdAt = null;
          if (createdAtStr != null) {
            createdAt = (createdAtStr != null ? ISO8601DateFormat.parse(createdAtStr) : null);
          }
          PersonFavourite personFavourite =
              new PersonFavourite(userName, siteInfo.getNodeRef(), Type.SITE, siteId, createdAt);
          sortedFavouriteNodes.put(personFavourite.getKey(), personFavourite);
        }
      } catch (AccessDeniedException ex) {
        // the user no longer has access to this site, skip over the favourite
        // TODO remove the favourite preference
        return;
      }
    }
  }
  @Override
  public PagingResults<PersonFavourite> getPagedFavourites(
      String userName,
      Set<Type> types,
      List<Pair<FavouritesService.SortFields, Boolean>> sortProps,
      PagingRequest pagingRequest) {
    // Get the user node reference
    final NodeRef personNodeRef = personService.getPerson(userName);
    if (personNodeRef == null) {
      throw new AlfrescoRuntimeException(
          "Can not get preferences for " + userName + " because he/she does not exist.");
    }

    boolean includeFiles = types.contains(Type.FILE);
    boolean includeFolders = types.contains(Type.FOLDER);
    boolean includeSites = types.contains(Type.SITE);

    String currentUserName = AuthenticationUtil.getFullyAuthenticatedUser();
    if (authenticationContext.isSystemUserName(currentUserName) == true
        || permissionService.hasPermission(personNodeRef, PermissionService.WRITE)
            == AccessStatus.ALLOWED
        || userName.equals(currentUserName) == true) {
      // we may have more than one favourite that is considered the same w.r.t. the PersonFavourite
      // comparator
      final Map<PersonFavouriteKey, PersonFavourite> sortedFavouriteNodes =
          new TreeMap<PersonFavouriteKey, PersonFavourite>(getComparator(sortProps));

      PrefKeys sitePrefKeys = getPrefKeys(Type.SITE);
      PrefKeys documentsPrefKeys = getPrefKeys(Type.FILE);
      PrefKeys foldersPrefKeys = getPrefKeys(Type.FOLDER);

      Map<String, Serializable> preferences = preferenceService.getPreferences(userName);
      for (String key : preferences.keySet()) {
        if (includeFiles && key.startsWith(documentsPrefKeys.sharePrefKey)) {
          String nodes = (String) preferences.get(key);
          if (nodes != null) {
            sortedFavouriteNodes.putAll(extractFavouriteNodes(userName, Type.FILE, nodes));
          }
        } else if (includeFolders && key.startsWith(foldersPrefKeys.sharePrefKey)) {
          String nodes = (String) preferences.get(key);
          if (nodes != null) {
            sortedFavouriteNodes.putAll(extractFavouriteNodes(userName, Type.FOLDER, nodes));
          }
        } else if (includeSites
            && key.startsWith(sitePrefKeys.getSharePrefKey())
            && !key.endsWith(".createdAt")) {
          // key is either of the form org.alfresco.share.sites.favourites.<siteId>.favourited or
          // org.alfresco.share.sites.favourites.<siteId>
          extractFavouriteSite(userName, Type.SITE, sortedFavouriteNodes, preferences, key);
        }
      }

      int totalSize = sortedFavouriteNodes.size();
      final PageDetails pageDetails = PageDetails.getPageDetails(pagingRequest, totalSize);

      final List<PersonFavourite> page = new ArrayList<PersonFavourite>(pageDetails.getPageSize());
      Iterator<PersonFavourite> it = sortedFavouriteNodes.values().iterator();
      for (int counter = 0; counter < pageDetails.getEnd() && it.hasNext(); counter++) {
        PersonFavourite favouriteNode = it.next();

        if (counter < pageDetails.getSkipCount()) {
          continue;
        }

        if (counter > pageDetails.getEnd() - 1) {
          break;
        }

        page.add(favouriteNode);
      }

      return new PagingResults<PersonFavourite>() {
        @Override
        public List<PersonFavourite> getPage() {
          return page;
        }

        @Override
        public boolean hasMoreItems() {
          return pageDetails.hasMoreItems();
        }

        @Override
        public Pair<Integer, Integer> getTotalResultCount() {
          Integer total = Integer.valueOf(sortedFavouriteNodes.size());
          return new Pair<Integer, Integer>(total, total);
        }

        @Override
        public String getQueryExecutionId() {
          return null;
        }
      };
    } else {
      // The current user does not have sufficient permissions to update the preferences for this
      // user
      throw new AccessDeniedException(
          "The current user "
              + currentUserName
              + " does not have sufficient permissions to get the favourites of the user "
              + userName);
    }
  }