Skip to content
Snippets Groups Projects
Commit fb078889 authored by Martin Lowe's avatar Martin Lowe :flag_ca:
Browse files

Merge branch 'malowe/master/1' into 'master'

Initial commit of commons code (WIP)

See merge request eclipsefdn/webdev/eclipsefdn-api-common!1
parents f78513df 444d29ec
No related branches found
No related tags found
1 merge request!1Initial commit of commons code (WIP)
Showing
with 1077 additions and 0 deletions
target/
.settings/
.project
.classpath
bin/
\ No newline at end of file
# Build JAVA applications using Apache Maven (http://maven.apache.org)
# For docker image tags see https://hub.docker.com/_/maven/
#
# For general lifecycle information see https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html
# This template will build and test your projects
# * Caches downloaded dependencies and plugins between invocation.
# * Verify but don't deploy merge requests.
# * Deploy built artifacts from master branch only.
variables:
# This will suppress any download for dependencies and plugins or upload messages which would clutter the console log.
# `showDateTime` will show the passed time in milliseconds. You need to specify `--batch-mode` to make this work.
MAVEN_OPTS: "-Dhttps.protocols=TLSv1.2 -Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository -Djava.awt.headless=true"
# As of Maven 3.3.0 instead of this you may define these options in `.mvn/maven.config` so the same config is used
# when running from the command line.
# `installAtEnd` and `deployAtEnd` are only effective with recent version of the corresponding plugins.
MAVEN_CLI_OPTS: "--batch-mode --errors --fail-at-end --show-version -DinstallAtEnd=true -DdeployAtEnd=true"
# This template uses jdk8 for verifying and deploying images
image: maven:3.3.9-jdk-8
# Cache downloaded dependencies and plugins between builds.
# To keep cache across branches add 'key: "$CI_JOB_NAME"'
cache:
paths:
- .m2/repository
# For merge requests do not `deploy` but only run `verify`.
# See https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html
.verify: &verify
stage: test
script:
- 'mvn $MAVEN_CLI_OPTS verify -Dconfig.secret.path=config/sample.secret.properties'
except:
- master
# Verify merge requests using JDK8
verify:jdk8:
<<: *verify
# To deploy packages from CI, create a ci_settings.xml file
# For deploying packages to GitLab's Maven Repository: See https://docs.gitlab.com/ee/user/project/packages/maven_repository.html#creating-maven-packages-with-gitlab-cicd for more details.
# Please note: The GitLab Maven Repository is currently only available in GitLab Premium / Ultimate.
# For `master` branch run `mvn deploy` automatically.
deploy:jdk8:
stage: deploy
script:
- if [ ! -f ci_settings.xml ];
then echo "CI settings missing\! If deploying to GitLab Maven Repository, please see https://docs.gitlab.com/ee/user/project/packages/maven_repository.html#creating-maven-packages-with-gitlab-cicd for instructions.";
fi
- 'mvn $MAVEN_CLI_OPTS deploy -s ci_settings.xml'
only:
- master
quarkus.oauth2.client-id=sample
quarkus.oauth2.client-secret=sample
%test.sample.secret.property=secret-value
%test.eclipse.secret.token=example
\ No newline at end of file
quarkus.oauth2.client-id=1b8R2q6ZE0MA7lISWxXbOeqL9
quarkus.oauth2.client-secret=bNzI5juUFRXjeiFgsM2x05XUY
quarkus.datasource.password = my-secret-pw
%test.sample.secret.property=secret-value
%test.eclipse.secret.token=example
/bin/
<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<artifactId>quarkus-core</artifactId>
<name>Core - Runtime</name>
<parent>
<groupId>org.eclipsefoundation</groupId>
<artifactId>quarkus-commons</artifactId>
<version>0.1-BETA</version>
<relativePath>../pom.xml</relativePath>
</parent>
<properties>
</properties>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-undertow</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jsonb</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-health</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-elytron-security-oauth2</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-cache</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-graphql</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.logmanager</groupId>
<artifactId>jboss-logmanager</artifactId>
<version>2.1.14.Final</version>
</dependency>
</dependencies>
</project>
/* Copyright (c) 2019 Eclipse Foundation and others.
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License 2.0
* which is available at http://www.eclipse.org/legal/epl-v20.html,
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipsefoundation.core.config;
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
import javax.json.bind.JsonbConfig;
import javax.json.bind.config.PropertyNamingStrategy;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import org.eclipsefoundation.core.helper.DateTimeHelper;
/**
* Updates JSONB config to use a naming convention when interacting with objects
* that match the API best practices set by internal documentation.
*
* @author Martin Lowe
*/
@Provider
public class JsonBConfig implements ContextResolver<Jsonb> {
@Override
public Jsonb getContext(Class<?> type) {
JsonbConfig config = new JsonbConfig();
// following strategy is defined as default by internal API guidelines
config.withPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CASE_WITH_UNDERSCORES)
.withDateFormat(DateTimeHelper.RAW_RFC_3339_FORMAT, null);
return JsonbBuilder.create(config);
}
}
package org.eclipsefoundation.core.config;
import java.util.function.Supplier;
import javax.enterprise.context.ApplicationScoped;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import io.quarkus.security.identity.AuthenticationRequestContext;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.identity.SecurityIdentityAugmentor;
import io.quarkus.security.runtime.QuarkusSecurityIdentity;
import io.smallrye.mutiny.Uni;
/**
* Custom override for production that can be enabled to set user roles to
* include the role set in the property, defaulting to admin access.
*
* @author Martin Lowe
*/
@ApplicationScoped
public class RoleAugmentor implements SecurityIdentityAugmentor {
// properties that allow this functionality to be configured
@ConfigProperty(name = "eclipse.oauth.override", defaultValue = "false")
boolean overrideRole;
@ConfigProperty(name = "eclipse.oauth.override.role", defaultValue = "marketplace_admin_access")
String overrideRoleName;
@Override
public int priority() {
return 0;
}
@Override
public Uni<SecurityIdentity> augment(SecurityIdentity identity, AuthenticationRequestContext context) {
return context.runBlocking(build(identity));
}
private Supplier<SecurityIdentity> build(SecurityIdentity identity) {
if (overrideRole) {
// create a new builder and copy principal, attributes, credentials and roles
// from the original
QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder()
.setPrincipal(identity.getPrincipal()).addAttributes(identity.getAttributes())
.addCredentials(identity.getCredentials()).addRoles(identity.getRoles());
// add custom role source here
builder.addRole(overrideRoleName);
// put the updated role in the future
return builder::build;
} else {
// put the unmodified identity in the future
return () -> identity;
}
}
}
\ No newline at end of file
/* Copyright (c) 2019 Eclipse Foundation and others.
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License 2.0
* which is available at http://www.eclipse.org/legal/epl-v20.html,
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipsefoundation.core.config;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.ServiceLoader;
import org.eclipse.microprofile.config.spi.ConfigSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Reads in a set of secret configuration values from the secret.properties file
* in the resources folder. These values are only secret in that they are set to
* be ignored by Git and should be set on a per-server basis.
*
* ConfigSource implementation was used to enable the usage of the
* {@link ServiceLoader} to load the configurations.
*
* @author Martin Lowe
*/
public class SecretConfigSource implements ConfigSource {
private static final Logger LOGGER = LoggerFactory.getLogger(SecretConfigSource.class);
public static final String PROPERTY_NAME = "config.secret.path";
public static final String ENV_NAME = "CONFIG_SECRET_PATH";
private Map<String, String> secrets;
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public Map<String, String> getProperties() {
if (secrets == null) {
this.secrets = new HashMap<>();
String secretPath = System.getProperty(PROPERTY_NAME);
// Fallback to checking env if not set in JVM
if (secretPath == null || secretPath.isEmpty()) {
secretPath = System.getenv(ENV_NAME);
}
if (secretPath == null || secretPath.isEmpty()) {
LOGGER.warn("Configuration '{}' not set, cannot generate secret properties.", PROPERTY_NAME);
return this.secrets;
}
// load the secrets file in
File f = new File(secretPath);
if (!f.exists() || !f.canRead()) {
LOGGER.error("File at path {} either does not exist or cannot be read", secretPath);
return this.secrets;
}
// read each of the lines of secret config that should be added
try (BufferedReader br = new BufferedReader(new FileReader(f))) {
Properties p = new Properties();
p.load(br);
secrets.putAll((Map) p);
} catch (IOException e) {
LOGGER.error("Error while reading in secrets configuration file.", e);
}
LOGGER.debug("Found secret keys: {}", secrets.keySet());
// add priority ordinal to map if missing. 260 ordinal sets the priority between
// container and environment variable priority.
secrets.computeIfAbsent(ConfigSource.CONFIG_ORDINAL, key -> "260");
}
return secrets;
}
@Override
public String getValue(String propertyName) {
return getProperties().get(propertyName);
}
@Override
public String getName() {
return "secret";
}
}
/* Copyright (c) 2019 Eclipse Foundation and others.
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License 2.0
* which is available at http://www.eclipse.org/legal/epl-v20.html,
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipsefoundation.core.exception;
/**
* @author martin
*
*/
public class MaintenanceException extends RuntimeException {
private static final long serialVersionUID = 1L;
}
/* Copyright (c) 2019 Eclipse Foundation and others.
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License 2.0
* which is available at http://www.eclipse.org/legal/epl-v20.html,
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipsefoundation.core.health;
import javax.enterprise.context.ApplicationScoped;
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipse.microprofile.health.Liveness;
import org.eclipse.microprofile.health.Readiness;
/**
* Basic health check to check the responsiveness of the API on a whole.
*
* @author Martin Lowe
*/
@Readiness
@Liveness
@ApplicationScoped
public class BaseApplicationHealthCheck implements HealthCheck {
@Override
public HealthCheckResponse call() {
return HealthCheckResponse.named("Basic health check").up().build();
}
}
/* Copyright (c) 2019 Eclipse Foundation and others.
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License 2.0
* which is available at http://www.eclipse.org/legal/epl-v20.html,
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipsefoundation.core.helper;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Date;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Central implementation for handling date time conversion in the service.
* Class uses Java8 DateTime formatters, creating an internal format that
* represents RFC 3339
*
* @author Martin Lowe
*/
public class DateTimeHelper {
private static final Logger LOGGER = LoggerFactory.getLogger(DateTimeHelper.class);
public static final String RAW_RFC_3339_FORMAT = "uuuu-MM-dd'T'HH:mm:ssXXX";
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(RAW_RFC_3339_FORMAT);
/**
* Converts RFC 3339 compliant date string to date object. If non compliant
* string is passed, issue is logged and null is returned. If negative UTC
* timezone (-00:00) is passed, UTC time zone is assumed.
*
* @param dateString an RFC 3339 date string.
* @return a date object representing time in date string, or null if not in RFC
* 3339 format.
*/
public static Date toRFC3339(String dateString) {
if (dateString.isBlank()) return null;
try {
return Date.from(ZonedDateTime.parse(dateString, formatter).toInstant());
} catch (DateTimeParseException e) {
LOGGER.warn("Could not parse date from string '{}'", dateString, e);
return null;
}
}
/**
* Converts passed date to RFC 3339 compliant date string. Time is adjusted to
* be in UTC time.
*
* @param date the date object to convert to RFC 3339 format.
* @return the RFC 3339 format date string.
*/
public static String toRFC3339(Date date) {
if (date == null) return null;
return formatter.format(date.toInstant().atZone(ZoneId.of("UTC")));
}
// hide constructor
private DateTimeHelper() {
}
}
package org.eclipsefoundation.core.helper;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;
import javax.ws.rs.core.MultivaluedMap;
import org.eclipsefoundation.core.namespace.UrlParameterNamespace;
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
import io.quarkus.runtime.Startup;
/**
* Helper for parameter lookups and filtering.
*
* @author Martin Lowe
*
*/
@Startup
@ApplicationScoped
public class ParameterHelper {
@Inject
Instance<UrlParameterNamespace> urlParamImpls;
// maintain internal list of key names to reduce churn. These are only created
// at build time
private List<String> urlParamNames;
/**
* Create the list of parameter key names once upon creation of this class
*/
@PostConstruct
void initialize() {
// use array list for better random access + lookups
this.urlParamNames = new ArrayList<>();
// populate a list with each
urlParamImpls.forEach(
namespace -> namespace.getParameters().stream().forEach(param -> urlParamNames.add(param.getName())));
}
/**
* Filters out unknown parameters by name/key. Creates and returns a new map
* with the known/tracked parameters. Tracked parameters are added through
* implementations of the {@link UrlParameterNamespace} interface.
*
* @param src multivalued parameter map to filter
* @return a map containing the filtered parameter values
*/
public MultivaluedMap<String, String> filterUnknownParameters(MultivaluedMap<String, String> src) {
MultivaluedMap<String, String> out = new MultivaluedMapImpl<>();
Set<String> keys = src.keySet();
for (String key : keys) {
if (urlParamNames.contains(key)) {
out.addAll(key, src.get(key));
}
}
return out;
}
/**
* Returns a list of parameter names, as generated from reading in the
* parameters present in the {@link UrlParameterNamespace} implementations.
*
* @return list of tracked parameter names.
*/
public List<String> getValidParameters() {
return new ArrayList<>(urlParamNames);
}
}
/* Copyright (c) 2019 Eclipse Foundation and others.
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License 2.0
* which is available at http://www.eclipse.org/legal/epl-v20.html,
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipsefoundation.core.helper;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.Objects;
import java.util.Optional;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.json.bind.Jsonb;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.xml.bind.DatatypeConverter;
import org.eclipsefoundation.core.model.RequestWrapper;
import org.eclipsefoundation.core.service.CachingService;
/**
* Helper class that transforms data into a response usable for the RESTeasy
* container. Uses injected JSON-B serializer and caching service to get current
* information on cache data.
*
* @author Martin Lowe
*
*/
@ApplicationScoped
public class ResponseHelper {
private static final MessageDigest DIGEST;
static {
try {
DIGEST = MessageDigest.getInstance("md5");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Could not create an MD5 hash digest");
}
}
@Inject
Jsonb jsonb;
@Inject
CachingService cachingService;
/**
* Builds a response using passed data. Uses references to the caching service
* and the current request to add information about ETags and Cache-Control
* headers.
*
* @param id the ID of the object to be stored in cache
* @param wrapper the query parameters for the current request
* @param data the data to attach to the response
* @param cachingService the cache for the current data request.
* @return a complete response object for the given data and request.
*/
public Response build(String id, RequestWrapper wrapper, MultivaluedMap<String, String> optParams, Object data,
Class<?> type) {
// set default cache control flags for API responses
CacheControl cc = new CacheControl();
cc.setNoStore(wrapper.isCacheBypass());
if (!cc.isNoStore()) {
cc.setMaxAge((int) cachingService.getMaxAge());
// get the TTL for the current entry
Optional<Long> ttl = cachingService.getExpiration(id, optParams == null ? wrapper.asMap() : optParams, type);
if (!ttl.isPresent()) {
return Response.serverError().build();
}
// serialize the data to get an etag
String content = jsonb.toJson(Objects.requireNonNull(data));
// ingest the content and hash to create an etag for current content
String hash;
synchronized (this) {
DIGEST.update(content.getBytes(StandardCharsets.UTF_8));
hash = DatatypeConverter.printHexBinary(DIGEST.digest());
DIGEST.reset();
}
// check if etag matches
String etag = wrapper.getHeader("Etag");
if (hash.equals(etag)) {
return Response.notModified(etag).cacheControl(cc).expires(new Date(ttl.get())).build();
}
// return a response w/ the generated etag
return Response.ok(data).tag(hash).cacheControl(cc).expires(new Date(ttl.get())).build();
}
return Response.ok(data).cacheControl(cc).build();
}
}
/*
* Copyright (C) 2019 Eclipse Foundation and others.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipsefoundation.core.model;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
public class Error {
private int statusCode;
private String message;
private String url;
public Error(int statusCode, String message) {
this.statusCode = statusCode;
this.message = message;
}
/**
* Creates an error object using the JAX-RS status to fetch the status code.
*
* @param status the JAX-RS status for the error.
* @param message the message to return
*/
public Error(Status status, String message) {
this.statusCode = status.getStatusCode();
this.message = message;
}
/**
* Returns a response object given the fields within as the contents.
*
* @return a JAX-RS Response with the given status code, with this object as the
* message.
*/
public Response asResponse() {
return Response.status(statusCode).entity(this).build();
}
/**
* @return the statusCode
*/
public int getStatusCode() {
return statusCode;
}
/**
* @return the message
*/
public String getMessage() {
return message;
}
/**
* @return the url
*/
public String getUrl() {
return url;
}
}
/* Copyright (c) 2019 Eclipse Foundation and others.
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License 2.0
* which is available at http://www.eclipse.org/legal/epl-v20.html,
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipsefoundation.core.model;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.UriInfo;
import org.eclipsefoundation.core.namespace.DeprecatedHeader;
import org.eclipsefoundation.core.namespace.RequestHeaderNames;
import org.eclipsefoundation.core.namespace.UrlParameterNamespace.UrlParameter;
import org.eclipsefoundation.core.request.CacheBypassFilter;
import org.jboss.resteasy.core.ResteasyContext;
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
import io.quarkus.arc.Unremovable;
import io.smallrye.graphql.api.Context;
/**
* Wrapper class for query parameter functionality, wrapping a Map of String to collection of String
* values. This class should be used anywhere where query parameters are used to control access and
* prevent bad data from being entered.
*
* @author Martin Lowe
*/
@Unremovable
@RequestScoped
public class RequestWrapper {
private static final String EMPTY_KEY_MESSAGE = "Key must not be null or blank";
private Map<String, List<String>> params;
@Inject Context context;
private UriInfo uriInfo;
private HttpServletRequest request;
private HttpServletResponse response;
/** Generates a wrapper around the context data available from the servlet container. */
public RequestWrapper() {
this.uriInfo = ResteasyContext.getContextData(UriInfo.class);
this.request = ResteasyContext.getContextData(HttpServletRequest.class);
this.response = ResteasyContext.getContextData(HttpServletResponse.class);
}
/**
* Retrieves the first value set in a list from the map for a given key.
*
* @param wrapper the parameter map containing the value
* @param key the key to retrieve the value for
* @return the first value set in the parameter map for the given key, or null if absent.
*/
public Optional<String> getFirstParam(UrlParameter key) {
if (key == null) {
throw new IllegalArgumentException(EMPTY_KEY_MESSAGE);
}
List<String> vals = getParams().get(key.getName());
if (vals == null || vals.isEmpty()) {
return Optional.empty();
}
return Optional.of(vals.get(0));
}
/**
* Retrieves the value list from the map for a given key.
*
* @param wrapper the parameter map containing the values
* @param key the key to retrieve the values for
* @return the value list for the given key if it exists, or an empty collection if none exists.
*/
public List<String> getParams(UrlParameter key) {
if (key == null) {
throw new IllegalArgumentException(EMPTY_KEY_MESSAGE);
}
List<String> vals = getParams().get(key.getName());
if (vals == null || vals.isEmpty()) {
return Collections.emptyList();
}
return vals;
}
/**
* Adds the given value for the given key, preserving previous values if they exist.
*
* @param key string key to add the value to, must not be null
* @param value the value to add to the key
*/
public void addParam(UrlParameter key, String value) {
if (key == null) {
throw new IllegalArgumentException(EMPTY_KEY_MESSAGE);
}
Objects.requireNonNull(value);
getParams().computeIfAbsent(key.getName(), k -> new ArrayList<>()).add(value);
}
/**
* Sets the value as the value for the given key, removing previous values if they exist.
*
* @param key string key to add the value to, must not be null
* @param value the value to add to the key
*/
public void setParam(UrlParameter key, String value) {
if (key == null) {
throw new IllegalArgumentException(EMPTY_KEY_MESSAGE);
}
Objects.requireNonNull(value);
// remove current value, and add new value in its place
getParams().remove(key.getName());
addParam(key, value);
}
/**
* Returns this QueryParams object as a Map of param values indexed by the param name.
*
* @return a copy of the internal param map
*/
public MultivaluedMap<String, String> asMap() {
MultivaluedMap<String, String> out = new MultivaluedMapImpl<>();
getParams().forEach(out::addAll);
return out;
}
private Map<String, List<String>> getParams() {
if (params == null) {
params = new HashMap<>();
if (uriInfo != null) {
params.putAll(uriInfo.getQueryParameters());
}
}
return this.params;
}
/**
* Returns the endpoint for the current call
*
* @return
*/
public String getEndpoint() {
String endpoint = "";
if (uriInfo != null) {
endpoint = uriInfo.getPath();
} else if (context != null) {
endpoint = context.getPath();
}
return endpoint;
}
/**
* Retrieve a request attribute
*
* @param key attribute key
* @return the attribute value, or an empty optional if missing.
*/
public Optional<Object> getAttribute(String key) {
return Optional.ofNullable(request.getAttribute(key));
}
/**
* Check whether the current request should bypass caching
*
* @return true if cache should be bypassed, otherwise false
*/
public boolean isCacheBypass() {
Object attr = request.getAttribute(CacheBypassFilter.ATTRIBUTE_NAME);
// if we have the attribute set on the request, return it. otherwise, false.
return attr instanceof Boolean ? (boolean) attr : Boolean.FALSE;
}
/**
* Retrieve a request header value.
*
* @param key the headers key value
* @return the value, or an empty optional if missing.
*/
public String getHeader(String key) {
return request.getHeader(key);
}
/**
* Retrieve the request version from the
*
* @param key the headers key value
* @return the version passed from the access version header
*/
public String getRequestVersion() {
return request.getHeader(RequestHeaderNames.ACCESS_VERSION);
}
/**
* Set the deprecation header in the response object for the client.
*
* @param d the date that the endpoint was deprecated
* @param msg information about the deprecation
*/
public void setDeprecatedHeader(Date d, String msg) {
response.setHeader(DeprecatedHeader.NAME, DeprecatedHeader.getValue(d, msg));
}
/**
* Set a header in the response object for the client.
*
* @param name the name of the header to set
* @param value the headers value
*/
public void setHeader(String name, String value) {
response.setHeader(name, value);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("RequestWrapper [");
sb.append("ip=").append(request.getRemoteAddr());
sb.append(", uri=").append(request.getRequestURI());
sb.append(", params=").append(getParams());
return sb.toString();
}
}
/*
* Copyright (C) 2019 Eclipse Foundation and others.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipsefoundation.core.namespace;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.inject.Singleton;
/**
* Namespace containing common URL parameters used throughout the API.
*
* @author Martin Lowe
*/
@Singleton
public final class DefaultUrlParameterNames implements UrlParameterNamespace {
public static final UrlParameter QUERY_STRING = new UrlParameter("q");
public static final UrlParameter PAGE = new UrlParameter("page");
public static final UrlParameter LIMIT = new UrlParameter("limit");
public static final UrlParameter IDS = new UrlParameter("ids");
public static final UrlParameter ID = new UrlParameter("id");
private static final List<UrlParameter> params = Collections.unmodifiableList(Arrays.asList(QUERY_STRING,PAGE,LIMIT,IDS,ID));
@Override
public List<UrlParameter> getParameters() {
return new ArrayList<>(params);
}
}
/* Copyright (c) 2019 Eclipse Foundation and others.
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License 2.0
* which is available at http://www.eclipse.org/legal/epl-v20.html,
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipsefoundation.core.namespace;
import java.util.Date;
import java.util.Objects;
/**
* Centralized location for creating the "Deprecated" header to be used when
* warning clients. This is designed to be used when endpoints/functionality are
* updated such that they diverge from previous versions.
*
* @author Martin Lowe
*
*/
public class DeprecatedHeader {
public static final String NAME = "Deprecated";
private Date date;
private String message;
/**
* Creates a valid deprecation header with the given values set.
*
* @param date the date that the endpoint/functionality was deprecated
* @param message information about the deprecation
*/
public DeprecatedHeader(Date date, String message) {
this.date = Objects.requireNonNull(date);
this.message = Objects.requireNonNull(message);
}
/**
* Get the value of a header that would contain the given values.
*
* @param date the date that the endpoint was deprecated
* @param message information about the deprecation
* @return the value for the header with given values.
*/
public static String getValue(Date date, String message) {
Objects.requireNonNull(date);
Objects.requireNonNull(message);
return date.toString() + ';' + message;
}
/**
* Get the value of the header for the current header.
*
* @return the value for this header.
*/
public String getValue() {
return DeprecatedHeader.getValue(this.date, this.message);
}
/**
* @return the date
*/
public Date getDate() {
return date;
}
/**
* @param date the date to set
*/
public void setDate(Date date) {
this.date = date;
}
/**
* @return the message
*/
public String getMessage() {
return message;
}
/**
* @param message the message to set
*/
public void setMessage(String message) {
this.message = message;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("DeprecatedHeader [date=");
builder.append(date);
builder.append(", message=");
builder.append(message);
builder.append("]");
return builder.toString();
}
}
package org.eclipsefoundation.core.namespace;
/**
* Contains Microprofile property names used by this application.
*
* @author Martin Lowe
*
*/
public class MicroprofilePropertyNames {
public static final String CACHE_TTL_MAX_SECONDS = "cache.ttl.write.seconds";
public static final String CACHE_SIZE_MAX = "cache.max.size";
private MicroprofilePropertyNames() {
}
}
/* Copyright (c) 2019 Eclipse Foundation and others.
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License 2.0
* which is available at http://www.eclipse.org/legal/epl-v20.html,
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipsefoundation.core.namespace;
/**
* Namespace for Eclipse-specific header names used in application.
*
* @author Martin Lowe
*/
public class RequestHeaderNames {
public static final String ACCESS_TOKEN = "Eclipse-Access-Token";
public static final String ACCESS_VERSION = "Access-Version";
private RequestHeaderNames() {}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment