This is an automated email from the ASF dual-hosted git repository. ahuber pushed a commit to branch v2 in repository https://gitbox.apache.org/repos/asf/isis.git
The following commit(s) were added to refs/heads/v2 by this push: new 1310040 ISIS-2006: initial prototype 1310040 is described below commit 13100407dbc33f3cb8108cad1b4f546bc757b7f4 Author: Andi Huber <ahu...@apache.org> AuthorDate: Mon Oct 15 22:35:56 2018 +0200 ISIS-2006: initial prototype Task-Url: https://issues.apache.org/jira/browse/ISIS-2006 --- .../applib/client/ActionParameterListBuilder.java | 102 +++++++++ .../apache/isis/applib/client/ResponseDigest.java | 174 +++++++++++++++ .../apache/isis/applib/client/RestfulClient.java | 237 +++++++++++++++++++++ .../isis/applib/client/RestfulClientConfig.java | 87 ++++++++ .../isis/applib/client/RestfulClientException.java | 40 ++++ .../apache/isis/applib/client/SuppressionType.java | 88 ++++++++ .../isis/applib/client/auth/BasicAuthFilter.java | 95 +++++++++ .../isis/commons/internal/base/_Strings.java | 18 ++ .../ContentNegotiationServiceOrgApacheIsisV1.java | 50 +---- 9 files changed, 843 insertions(+), 48 deletions(-) diff --git a/core/applib/src/main/java/org/apache/isis/applib/client/ActionParameterListBuilder.java b/core/applib/src/main/java/org/apache/isis/applib/client/ActionParameterListBuilder.java new file mode 100644 index 0000000..2ed05be --- /dev/null +++ b/core/applib/src/main/java/org/apache/isis/applib/client/ActionParameterListBuilder.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.isis.applib.client; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Collectors; + +import javax.ws.rs.client.Entity; + +/** + * + * @since 2.0.0-M2 + */ +public class ActionParameterListBuilder { + + private final Map<String, String> actionParameters = new LinkedHashMap<>(); + + public ActionParameterListBuilder addActionParameter(String parameterName, String parameterValue) { + actionParameters.put(parameterName, parameterValue != null + ? value("\"" + parameterValue + "\"") + : value(JSON_NULL_LITERAL)); + return this; + } + + public ActionParameterListBuilder addActionParameter(String parameterName, int parameterValue) { + actionParameters.put(parameterName, value(""+parameterValue)); + return this; + } + + public ActionParameterListBuilder addActionParameter(String parameterName, long parameterValue) { + actionParameters.put(parameterName, value(""+parameterValue)); + return this; + } + + public ActionParameterListBuilder addActionParameter(String parameterName, byte parameterValue) { + actionParameters.put(parameterName, value(""+parameterValue)); + return this; + } + + public ActionParameterListBuilder addActionParameter(String parameterName, short parameterValue) { + actionParameters.put(parameterName, value(""+parameterValue)); + return this; + } + + public ActionParameterListBuilder addActionParameter(String parameterName, double parameterValue) { + actionParameters.put(parameterName, value(""+parameterValue)); + return this; + } + + public ActionParameterListBuilder addActionParameter(String parameterName, float parameterValue) { + actionParameters.put(parameterName, value(""+parameterValue)); + return this; + } + + public ActionParameterListBuilder addActionParameter(String parameterName, boolean parameterValue) { + actionParameters.put(parameterName, value(""+parameterValue)); + return this; + } + + public Entity<String> build() { + + final StringBuilder sb = new StringBuilder(); + sb.append("{\n") + .append(actionParameters.entrySet().stream() + .map(this::toJson) + .collect(Collectors.joining(",\n"))) + .append("\n}"); + + return Entity.json(sb.toString()); + } + + // -- HELPER + + private final static String JSON_NULL_LITERAL = "null"; + + private String value(String valueLiteral) { + return "{\"value\" : " + valueLiteral + "}"; + } + + private String toJson(Map.Entry<String, String> entry) { + return " \""+entry.getKey()+"\": "+entry.getValue(); + } + + +} diff --git a/core/applib/src/main/java/org/apache/isis/applib/client/ResponseDigest.java b/core/applib/src/main/java/org/apache/isis/applib/client/ResponseDigest.java new file mode 100644 index 0000000..9623c8c --- /dev/null +++ b/core/applib/src/main/java/org/apache/isis/applib/client/ResponseDigest.java @@ -0,0 +1,174 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.isis.applib.client; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.NoSuchElementException; +import java.util.concurrent.Future; +import java.util.function.Function; + +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status.Family; + +import org.apache.isis.commons.internal.base._Strings; + +/** + * + * @since 2.0.0-M2 + */ +public class ResponseDigest<T> { + + /** synchronous response processing */ + public static <T> ResponseDigest<T> of(Response response, Class<T> entityType) { + return new ResponseDigest<>(response, entityType).digest(); + } + + /** a-synchronous response failure processing */ + public static <T> ResponseDigest<T> ofAsyncFailure( + Future<Response> asyncResponse, + Class<T> entityType, + Exception failure) { + + Response response; + try { + response = asyncResponse.isDone() ? asyncResponse.get() : null; + } catch (Exception e) { + response = null; + } + + final ResponseDigest<T> failureDigest = new ResponseDigest<>(response, entityType); + return failureDigest.digestAsyncFailure(asyncResponse.isCancelled(), failure); + } + + private final Response response; + private final Class<T> entityType; + + private T entity; + private Exception failureCause; + + + protected ResponseDigest(Response response, Class<T> entityType) { + this.response = response; + this.entityType = entityType; + } + + public boolean isSuccess() { + return !isFailure(); + } + + public boolean isFailure() { + return failureCause!=null; + } + + public T get(){ + return entity; + } + + public Exception getFailureCause(){ + return failureCause; + } + + public T ifSuccessGetOrElseMap(Function<Exception, T> failureMapper) { + return isSuccess() + ? get() + : failureMapper.apply(getFailureCause()); + } + + public <X> X ifSuccessMapOrElseMap(Function<T, X> successMapper, Function<Exception, X> failureMapper) { + return isSuccess() + ? successMapper.apply(get()) + : failureMapper.apply(getFailureCause()); + } + + // -- HELPER + + private ResponseDigest<T> digest() { + + if(response==null) { + entity = null; + failureCause = new NoSuchElementException(); + return this; + } + + if(!response.hasEntity()) { + entity = null; + failureCause = new NoSuchElementException(defaultFailureMessage(response)); + return this; + } + + if(response.getStatusInfo().getFamily() != Family.SUCCESSFUL) { + entity = null; + failureCause = new RestfulClientException(defaultFailureMessage(response)); + return this; + } + + try { + entity = response.readEntity(entityType); + } catch (Exception e) { + entity = null; + failureCause = new RestfulClientException("failed to read JAX-RS response content", e); + } + + return this; + } + + private ResponseDigest<T> digestAsyncFailure(boolean isCancelled, Exception failure) { + + entity = null; + + + if(isCancelled) { + failureCause = new RestfulClientException("Async JAX-RS request was canceled", failure); + return this; + } + + if(response==null) { + failureCause = new RestfulClientException("Async JAX-RS request failed", failure); + return this; + } + + failureCause = new RestfulClientException("Async JAX-RS request failed " + + defaultFailureMessage(response), failure); + return this; + + } + + private String defaultFailureMessage(Response response) { + String failureMessage = "non-successful JAX-RS response: " + + String.format("%s (Http-Status-Code: %d)", + response.getStatusInfo().getReasonPhrase(), + response.getStatus()); + + if(response.hasEntity()) { + try { + String jsonContent = _Strings.read((InputStream) response.getEntity(), StandardCharsets.UTF_8); + return failureMessage + "\nContent:\n" + jsonContent; + } catch (Exception e) { + // ignore + } + } + + return failureMessage; + } + + + + +} diff --git a/core/applib/src/main/java/org/apache/isis/applib/client/RestfulClient.java b/core/applib/src/main/java/org/apache/isis/applib/client/RestfulClient.java new file mode 100644 index 0000000..a96cbae --- /dev/null +++ b/core/applib/src/main/java/org/apache/isis/applib/client/RestfulClient.java @@ -0,0 +1,237 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.isis.applib.client; + +import static org.apache.isis.commons.internal.base._NullSafe.stream; + +import java.util.EnumSet; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; +import java.util.stream.Collectors; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.Invocation.Builder; +import javax.ws.rs.core.Response; + +import org.apache.isis.applib.client.auth.BasicAuthFilter; +import org.apache.isis.applib.client.auth.BasicAuthFilter.Credentials; +import org.apache.isis.commons.internal.base._Strings; +import org.apache.isis.commons.internal.context._Context; + +/** + * Setup the Restful Client with Basic-Auth: + * <pre> +RestfulClientConfig clientConfig = new RestfulClientConfig(); +clientConfig.setRestfulBase("http://localhost:8080/helloworld/restful/"); +// setup basic-auth +clientConfig.setUseBasicAuth(true); +clientConfig.setRestfulAuthUser("sven"); +clientConfig.setRestfulAuthPassword("pass"); + +RestfulClient client = RestfulClient.ofConfig(clientConfig); + * </pre> + * + * Synchronous example: + * <pre> + +Builder request = client.request( + "services/myService/actions/lookupMyObjectById/invoke", + SuppressionType.setOf(SuppressionType.RO)); + +Entity<String> args = client.arguments() + .addActionParameter("id", "12345") + .build(); + +Response response = request.post(args); + +ResponseDigest<MyObject> digest = client.digest(response, MyObject.class); + +if(digest.isSuccess()) { + System.out.println("result: "+ digest.get().get$$instanceId()); +} else { + digest.getFailureCause().printStackTrace(); +} + * </pre> + * Asynchronous example: + * <pre> + +Builder request = client.request( + "services/myService/actions/lookupMyObjectById/invoke", + SuppressionType.setOf(SuppressionType.RO)); + +Entity<String> args = client.arguments() + .addActionParameter("id", "12345") + .build(); + +Response response = request + .async() + .post(args); + +CompletableFuture<ResponseDigest<MyObject>> digestFuture = + client.digest(response, MyObject.class); + +ResponseDigest<MyObject> digest = digestFuture.get(); // blocking + +if(digest.isSuccess()) { + System.out.println("result: "+ digest.get().get$$instanceId()); +} else { + digest.getFailureCause().printStackTrace(); +} + * </pre> + * + * Maven Setup: + * <pre>{@code +<dependency> + <groupId>org.apache.isis.core</groupId> + <artifactId>isis-core-applib</artifactId> + <version>2.0.0-M2-SNAPSHOT</version> +</dependency> +<dependency> + <groupId>javax.ws.rs</groupId> + <artifactId>javax.ws.rs-api</artifactId> + <version>2.1.1</version> +</dependency> +<dependency> + <groupId>org.glassfish.jersey.core</groupId> + <artifactId>jersey-client</artifactId> + <version>2.25.1</version> +</dependency> +<dependency> + <groupId>org.eclipse.persistence</groupId> + <artifactId>org.eclipse.persistence.moxy</artifactId> + <version>2.6.0</version> +</dependency>} + * </pre> + * + * @since 2.0.0-M2 + */ +public class RestfulClient { + + public static String DEFAULT_RESPONSE_CONTENT_TYPE = "application/json;profile=urn:org.apache.isis/v1"; + + private RestfulClientConfig clientConfig; + private Client client; + + public static RestfulClient ofConfig(RestfulClientConfig clientConfig) { + RestfulClient restClient = new RestfulClient(); + restClient.init(clientConfig); + return restClient; + } + + public void init(RestfulClientConfig clientConfig) { + this.clientConfig = clientConfig; + client = ClientBuilder.newClient(); + + if(clientConfig.isUseBasicAuth()){ + final Credentials credentials = Credentials.of( + clientConfig.getRestfulAuthUser(), + clientConfig.getRestfulAuthPassword()); + client.register(BasicAuthFilter.of(credentials)); + } + + try { + Class<?> MOXyJsonProvider = _Context.loadClass("org.eclipse.persistence.jaxb.rs.MOXyJsonProvider"); + client.register(MOXyJsonProvider); + } catch (Exception e) { + // this is just provided for convenience + } + + } + + public RestfulClientConfig getConfig() { + return clientConfig; + } + + public Client getJaxRsClient() { + return client; + } + + // -- REQUEST BUILDER + + public Builder request(String path, SuppressionType ... suppressionTypes) { + return request(path, SuppressionType.setOf(suppressionTypes)); + } + + public Builder request(String path, EnumSet<SuppressionType> suppressionTypes) { + final String responseContentType = DEFAULT_RESPONSE_CONTENT_TYPE + + toSuppressionLiteral(suppressionTypes); + + return client.target(relativePathToUri(path)).request(responseContentType); + } + + // -- ARGUMENT BUILDER + + public ActionParameterListBuilder arguments() { + return new ActionParameterListBuilder(); + } + + // -- RESPONSE PROCESSING (SYNC) + + public <T> ResponseDigest<T> digest(Response response, Class<T> entityType) { + return ResponseDigest.of(response, entityType); + } + + // -- RESPONSE PROCESSING (ASYNC) + + public <T> CompletableFuture<ResponseDigest<T>> digest( + final Future<Response> asyncResponse, + final Class<T> entityType) { + + final CompletableFuture<ResponseDigest<T>> completableFuture = CompletableFuture.supplyAsync(()->{ + try { + Response response = asyncResponse.get(); + ResponseDigest<T> digest = digest(response, entityType); + + return digest; + + } catch (Exception e) { + return ResponseDigest.ofAsyncFailure(asyncResponse, entityType, e); + } + }); + + return completableFuture; + } + + // -- HELPER + + private String relativePathToUri(String path) { + final String baseUri = _Strings.suffix(clientConfig.getRestfulBase(), "/"); + while(path.startsWith("/")) { + path = path.substring(1); + } + return baseUri + path; + } + + private String toSuppressionLiteral(EnumSet<SuppressionType> suppressionTypes) { + final String suppressionSetLiteral = stream(suppressionTypes) + .map(SuppressionType::name) + .collect(Collectors.joining(",")); + + if(_Strings.isNotEmpty(suppressionSetLiteral)) { + return ";suppress=" + suppressionSetLiteral; + } + + return ""; + } + + + + +} diff --git a/core/applib/src/main/java/org/apache/isis/applib/client/RestfulClientConfig.java b/core/applib/src/main/java/org/apache/isis/applib/client/RestfulClientConfig.java new file mode 100644 index 0000000..c0578b1 --- /dev/null +++ b/core/applib/src/main/java/org/apache/isis/applib/client/RestfulClientConfig.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.isis.applib.client; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * + * @since 2.0.0-M2 + */ +@XmlRootElement(name="restful-client-config") +@XmlAccessorType(XmlAccessType.FIELD) +public class RestfulClientConfig { + + // -- + + @XmlElement(name="restfulBase") + private String restfulBase; + + public String getRestfulBase() { + return restfulBase; + } + + public void setRestfulBase(String restfulBase) { + this.restfulBase = restfulBase; + } + + // -- + + @XmlElement(name="useBasicAuth") + private boolean useBasicAuth; + + public boolean isUseBasicAuth() { + return useBasicAuth; + } + + public void setUseBasicAuth(boolean useBasicAuth) { + this.useBasicAuth = useBasicAuth; + } + + // -- + + @XmlElement(name="restfulAuthUser") + private String restfulAuthUser; + + public String getRestfulAuthUser() { + return restfulAuthUser; + } + + public void setRestfulAuthUser(String restfulAuthUser) { + this.restfulAuthUser = restfulAuthUser; + } + + // -- + + @XmlElement(name="restfulAuthPassword") + private String restfulAuthPassword; + + public String getRestfulAuthPassword() { + return restfulAuthPassword; + } + + public void setRestfulAuthPassword(String restfulAuthPassword) { + this.restfulAuthPassword = restfulAuthPassword; + } + + +} diff --git a/core/applib/src/main/java/org/apache/isis/applib/client/RestfulClientException.java b/core/applib/src/main/java/org/apache/isis/applib/client/RestfulClientException.java new file mode 100644 index 0000000..2aaa99a --- /dev/null +++ b/core/applib/src/main/java/org/apache/isis/applib/client/RestfulClientException.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.isis.applib.client; + +public class RestfulClientException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public RestfulClientException() { + } + + public RestfulClientException(final String message) { + super(message); + } + + public RestfulClientException(final Throwable cause) { + super(cause); + } + + public RestfulClientException(final String message, final Throwable cause) { + super(message, cause); + } + +} diff --git a/core/applib/src/main/java/org/apache/isis/applib/client/SuppressionType.java b/core/applib/src/main/java/org/apache/isis/applib/client/SuppressionType.java new file mode 100644 index 0000000..78467f1 --- /dev/null +++ b/core/applib/src/main/java/org/apache/isis/applib/client/SuppressionType.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.isis.applib.client; + +import static org.apache.isis.commons.internal.base._NullSafe.stream; + +import java.util.EnumSet; +import java.util.List; + +import org.apache.isis.commons.internal.base._NullSafe; + +/** + * + * @since 2.0.0-M2 + */ +public enum SuppressionType { + + /** suppress '$$RO', RO Spec representation*/ + RO, + + /** suppress '$$href', hyperlink to the representation*/ + HREF, + + /** suppress '$$instanceId', instance id of the domain object*/ + ID, + + /** suppress '$$title', title of the domain object*/ + TITLE, + + /** suppress all '$$...' entries*/ + ALL + ; + + public static EnumSet<SuppressionType> setOf(SuppressionType ... types){ + final EnumSet<SuppressionType> set = EnumSet.noneOf(SuppressionType.class); + stream(types).forEach(set::add); + return set; + } + + public static class ParseUtil { + + public static EnumSet<SuppressionType> parse(List<String> parameterList) { + final EnumSet<SuppressionType> set = EnumSet.noneOf(SuppressionType.class); + parameterList.stream() + .map(SuppressionType.ParseUtil::parseOrElseNull) + .filter(_NullSafe::isPresent) + .forEach(set::add); + if(set.contains(ALL)) { + return EnumSet.allOf(SuppressionType.class); + } + return set; + } + + private static SuppressionType parseOrElseNull(String literal) { + + // honor pre v2 behavior + if("true".equalsIgnoreCase(literal)) { + return SuppressionType.RO; + } + + try { + return SuppressionType.valueOf(literal.toUpperCase()); + } catch (IllegalArgumentException e) { + return null; + } + } + + } + + + +} \ No newline at end of file diff --git a/core/applib/src/main/java/org/apache/isis/applib/client/auth/BasicAuthFilter.java b/core/applib/src/main/java/org/apache/isis/applib/client/auth/BasicAuthFilter.java new file mode 100644 index 0000000..3f4f39e --- /dev/null +++ b/core/applib/src/main/java/org/apache/isis/applib/client/auth/BasicAuthFilter.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.isis.applib.client.auth; + +import static org.apache.isis.commons.internal.base._With.requires; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; + +import javax.ws.rs.client.ClientRequestContext; +import javax.ws.rs.client.ClientRequestFilter; +import javax.xml.bind.DatatypeConverter; + +import org.apache.isis.commons.internal.base._Strings; + +/** + * + * @since 2.0.0-M2 + */ +public class BasicAuthFilter implements ClientRequestFilter { + + /** + * + * @since 2.0.0-M2 + */ + public static class Credentials { + final String user; + final String pass; + public static Credentials empty() { + return new Credentials("anonymous", null); + } + public static Credentials of(String user, String pass) { + if(_Strings.isNullOrEmpty(user)) { + return empty(); + } + return new Credentials(user, pass); + } + private Credentials(String user, String pass) { + this.user = user; + this.pass = pass; + } + @Override + public String toString() { + return "" + user + ":" + pass; + } + } + + public static BasicAuthFilter of(Credentials credentials) { + BasicAuthFilter filter = new BasicAuthFilter(); + filter.setCredentials(credentials); + return filter; + } + + private Credentials credentials = Credentials.empty(); + + public Credentials getCredentials() { + return credentials; + } + + public void setCredentials(Credentials credentials) { + this.credentials = requires(credentials, "credentials"); + } + + @Override + public void filter(ClientRequestContext requestContext) throws IOException { + requestContext.getHeaders().add("Authorization", getAuthorizationValue()); + } + + // -- HELPER + + private String getAuthorizationValue() { + try { + return "Basic " + DatatypeConverter.printBase64Binary(credentials.toString().getBytes("UTF-8")); + } catch (UnsupportedEncodingException ex) { + throw new IllegalStateException("Cannot encode with UTF-8", ex); + } + } + +} \ No newline at end of file diff --git a/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings.java b/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings.java index d75b615..38fd902 100644 --- a/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings.java +++ b/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings.java @@ -24,9 +24,11 @@ import static org.apache.isis.commons.internal.base._Strings_SplitIterator.split import static org.apache.isis.commons.internal.base._With.mapIfPresentElse; import static org.apache.isis.commons.internal.base._With.requires; +import java.io.InputStream; import java.nio.charset.Charset; import java.util.Arrays; import java.util.Map; +import java.util.Scanner; import java.util.Spliterator; import java.util.Spliterators; import java.util.function.UnaryOperator; @@ -332,6 +334,20 @@ public final class _Strings { requires(replacement, "replacement"); return mapIfPresentElse(input, __->input.replaceAll("\\s+", replacement), null); } + + // -- READ FROM INPUT STREAM + + public static String read(@Nullable final InputStream input, Charset charset) { + requires(charset, "charset"); + if(input==null) { + return ""; + } + // see https://stackoverflow.com/questions/309424/how-to-read-convert-an-inputstream-into-a-string-in-java + try(Scanner scanner = new Scanner(input, charset.name())){ + scanner.useDelimiter("\\A"); + return scanner.hasNext() ? scanner.next() : ""; + } + } // -- BYTE ARRAY CONVERSION @@ -424,5 +440,7 @@ public final class _Strings { return suffix(fileName, prefix(fileExtension, ".")); } + + } diff --git a/core/viewer-restfulobjects-rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceOrgApacheIsisV1.java b/core/viewer-restfulobjects-rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceOrgApacheIsisV1.java index 94ba534..2534fc9 100644 --- a/core/viewer-restfulobjects-rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceOrgApacheIsisV1.java +++ b/core/viewer-restfulobjects-rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceOrgApacheIsisV1.java @@ -29,7 +29,7 @@ import org.apache.isis.applib.annotation.DomainService; import org.apache.isis.applib.annotation.NatureOfService; import org.apache.isis.applib.annotation.Programmatic; import org.apache.isis.applib.annotation.Where; -import org.apache.isis.commons.internal.base._NullSafe; +import org.apache.isis.applib.client.SuppressionType; import org.apache.isis.core.metamodel.adapter.ObjectAdapter; import org.apache.isis.core.metamodel.consent.Consent; import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy; @@ -54,52 +54,6 @@ import org.apache.isis.viewer.restfulobjects.rendering.service.RepresentationSer ) public class ContentNegotiationServiceOrgApacheIsisV1 extends ContentNegotiationServiceAbstract { - public static enum SuppressionType { - - /** suppress '$$RO', RO Spec representation*/ - RO, - - /** suppress '$$href', hyperlink to the representation*/ - HREF, - - /** suppress '$$instanceId', instance id of the domain object*/ - ID, - - /** suppress '$$title', title of the domain object*/ - TITLE, - - /** suppress all '$$...' entries*/ - ALL - ; - - public static EnumSet<SuppressionType> parse(List<String> parameterList) { - final EnumSet<SuppressionType> set = EnumSet.noneOf(SuppressionType.class); - parameterList.stream() - .map(SuppressionType::parseOrElseNull) - .filter(_NullSafe::isPresent) - .forEach(set::add); - if(set.contains(ALL)) { - return EnumSet.allOf(SuppressionType.class); - } - return set; - } - - private static SuppressionType parseOrElseNull(String literal) { - - // honor pre v2 behavior - if("true".equalsIgnoreCase(literal)) { - return SuppressionType.RO; - } - - try { - return SuppressionType.valueOf(literal.toUpperCase()); - } catch (IllegalArgumentException e) { - return null; - } - } - - } - /** * Unlike RO v1.0, use a single content-type of <code>application/json;profile="urn:org.apache.isis/v1"</code>. * @@ -344,7 +298,7 @@ public class ContentNegotiationServiceOrgApacheIsisV1 extends ContentNegotiation protected EnumSet<SuppressionType> suppress( final RepresentationService.Context2 rendererContext) { final List<MediaType> acceptableMediaTypes = rendererContext.getAcceptableMediaTypes(); - return SuppressionType.parse(mediaTypeParameterList(acceptableMediaTypes, "suppress")); + return SuppressionType.ParseUtil.parse(mediaTypeParameterList(acceptableMediaTypes, "suppress")); } private void appendObjectTo(