@Override
  public Set<String> getTagsSuggest(String url) {
    String hashUrl = MD5Util.md5Hex(url);

    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<Tuple> query = cb.createTupleQuery();

    Root<Bookmark> root = query.from(Bookmark.class);
    // joins
    SetJoin<Bookmark, String> tagsJoin = root.joinSet("tags");

    query.select(cb.tuple(tagsJoin.alias("tag"), cb.count(tagsJoin).alias("count_tags")));
    query.where(cb.equal(root.get("hashUrl"), hashUrl));
    query.groupBy(tagsJoin);
    query.orderBy(cb.desc(cb.count(tagsJoin)));

    // top five tags
    List<Tuple> result = entityManager.createQuery(query).setMaxResults(5).getResultList();

    Set<String> topTags = new HashSet<String>();

    for (Tuple t : result) {
      topTags.add((String) t.get("tag"));
    }

    return topTags;
  }
  @Override
  public Map<String, Long> countTagsByUser(String userName) {
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<Tuple> query = cb.createTupleQuery();

    Root<Bookmark> root = query.from(Bookmark.class);
    // joins
    SetJoin<Bookmark, String> tagsJoin = root.joinSet("tags");
    Join<Bookmark, User> userJoin = root.join("user");

    query.select(cb.tuple(tagsJoin.alias("tag"), cb.count(tagsJoin).alias("count_tags")));
    query.where(cb.equal(userJoin.get("userName"), userName));
    query.groupBy(tagsJoin);
    query.orderBy(cb.desc(cb.count(tagsJoin)));

    List<Tuple> result = entityManager.createQuery(query).setMaxResults(30).getResultList();

    Map<String, Long> tagRanking = new TreeMap<String, Long>();

    for (Tuple t : result) {
      tagRanking.put((String) t.get("tag"), (Long) t.get("count_tags"));
    }

    return tagRanking;
  }
  public Predicate addFilter(
      Root<UserEntity> root, FilterConstraint constraint, String value, CriteriaBuilder cb) {

    if (constraint != FilterConstraint.PRESENT
        && (field.getType() == ExtensionFieldType.INTEGER
            || field.getType() == ExtensionFieldType.DECIMAL)) {

      value = numberPadder.pad(value);
    }

    final SetJoin<UserEntity, ExtensionFieldValueEntity> join =
        createOrGetJoin(
            generateAlias(urn + "." + field.getName()), root, UserEntity_.extensionFieldValues);

    Predicate filterPredicate =
        constraint.createPredicateForExtensionField(
            join.get(ExtensionFieldValueEntity_.value), value, field, cb);

    Predicate valueBelongsToField =
        cb.equal(
            join.get(ExtensionFieldValueEntity_.extensionField)
                .get(ExtensionFieldEntity_.internalId),
            field.getInternalId());

    join.on(valueBelongsToField);

    return filterPredicate;
  }
  @SuppressWarnings("unchecked")
  protected SetJoin<UserEntity, ExtensionFieldValueEntity> createOrGetJoin(
      String alias,
      Root<UserEntity> root,
      SetAttribute<UserEntity, ExtensionFieldValueEntity> attribute) {

    for (Join<UserEntity, ?> currentJoin : root.getJoins()) {
      if (currentJoin.getAlias() == null) {
        // if alias is null, it is not an alias for an extension join, so we ignore it
        continue;
      }

      if (currentJoin.getAlias().equals(alias)) {
        return (SetJoin<UserEntity, ExtensionFieldValueEntity>) currentJoin;
      }
    }

    final SetJoin<UserEntity, ExtensionFieldValueEntity> join = root.join(attribute, JoinType.LEFT);

    join.alias(alias);

    return join;
  }