/*******************************************************************************
* 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() {}
}