/******************************************************************************* * Copyright (c) 2012-2017 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.api.core.util; import org.eclipse.che.api.core.Page; import org.eclipse.che.commons.lang.Pair; import javax.ws.rs.core.UriBuilder; import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; import static java.util.Collections.emptyMap; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.joining; /** * Provides useful methods for working with {@link Page} instances * and pageable uris management. * * @author Yevhenii Voevodin */ public final class PagingUtil { private static final String LINK_HEADER_SEPARATOR = ", "; /** * Helps to retrieve href along with rel from link header value part. * Value format is {@literal <http://host:port/path?query=value>; rel="next"}, * so the first group is href while the second group is rel. */ private static final Pattern LINK_HEADER_REGEX = Pattern.compile("<(?<href>.+)>;.*rel=\"(?<rel>.+)\".*"); /** * Generates link header value from the page object and base uri. * <a href="https://tools.ietf.org/html/rfc5988">The Link header spec</a> * * @param page * the page used to generate link * @param uri * the uri which is used for adding {@code skipCount} & {@code maxItems} query parameters * @return 'Link' header value * @throws NullPointerException * when either {@code page} or {@code uri} is null */ public static String createLinkHeader(Page<?> page, URI uri) { requireNonNull(page, "Required non-null page"); requireNonNull(uri, "Required non-null uri"); final ArrayList<Pair<String, Page.PageRef>> pageRefs = new ArrayList<>(4); pageRefs.add(Pair.of("first", page.getFirstPageRef())); pageRefs.add(Pair.of("last", page.getLastPageRef())); if (page.hasPreviousPage()) { pageRefs.add(Pair.of("prev", page.getPreviousPageRef())); } if (page.hasNextPage()) { pageRefs.add(Pair.of("next", page.getNextPageRef())); } final UriBuilder ub = UriBuilder.fromUri(uri); return pageRefs.stream() .map(refPair -> format("<%s>; rel=\"%s\"", ub.clone() .replaceQueryParam("skipCount", refPair.second.getItemsBefore()) .replaceQueryParam("maxItems", refPair.second.getPageSize()) .build() .toString(), refPair.first)) .collect(joining(LINK_HEADER_SEPARATOR)); } /** * Returns REL to URI map based on the given {@code linkHeader} value. * If the {@code linkHeader} is null or empty then an empty map will be returned. * * <p>Note that link header is parsed due to the {@link #createLinkHeader(Page, URI)} method strategy. * * @param linkHeader * link header value */ public static Map<String, String> parseLinkHeader(String linkHeader) { if (isNullOrEmpty(linkHeader)) { return emptyMap(); } final Map<String, String> res = new HashMap<>(); for (String part : linkHeader.split(LINK_HEADER_SEPARATOR)) { final Matcher matcher = LINK_HEADER_REGEX.matcher(part); if (matcher.matches()) { res.put(matcher.group("rel"), matcher.group("href")); } } return res; } private PagingUtil() {} }