diff --git a/examples/hawkbit-example-ddi-client/pom.xml b/examples/hawkbit-example-ddi-client/pom.xml index 7630a5a9e..8ce1749dd 100644 --- a/examples/hawkbit-example-ddi-client/pom.xml +++ b/examples/hawkbit-example-ddi-client/pom.xml @@ -7,20 +7,54 @@ hawkbit-examples-parent 0.2.0-SNAPSHOT - org.eclipse.hawkbit hawkbit-example-ddi-client - 0.2.0-SNAPSHOT + hawkbit-example-ddi-client - http://maven.apache.org - - UTF-8 - + + + + + org.springframework.cloud + spring-cloud-netflix + 1.0.7.RELEASE + pom + import + + + + - + + org.eclipse.hawkbit + hawkbit-ddi-api + ${project.version} + + + org.springframework.cloud + spring-cloud-starter-feign + + + com.netflix.feign + feign-core + + 8.16.0 + + + com.netflix.feign + feign-jackson + + 8.16.0 + + + javax.servlet + javax.servlet-api + + + + junit junit - 3.8.1 test - + diff --git a/examples/hawkbit-example-ddi-client/src/main/java/org/eclipse/hawkbit/ddi/client/Application.java b/examples/hawkbit-example-ddi-client/src/main/java/org/eclipse/hawkbit/ddi/client/Application.java new file mode 100644 index 000000000..e230a77da --- /dev/null +++ b/examples/hawkbit-example-ddi-client/src/main/java/org/eclipse/hawkbit/ddi/client/Application.java @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2011-2016 Bosch Software Innovations GmbH, Germany. All rights reserved. + */ +package org.eclipse.hawkbit.ddi.client; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.cloud.netflix.feign.EnableFeignClients; + +/** + * @author Jonathan Knoblauch + * + */ +@SpringBootApplication +@EnableFeignClients +public class Application { + + public static void main(final String[] args) { + new SpringApplicationBuilder().showBanner(false).sources(Application.class).run(args); + } + +} diff --git a/examples/hawkbit-example-ddi-client/src/main/java/org/eclipse/hawkbit/ddi/client/ApplicationJsonRequestHeaderInterceptor.java b/examples/hawkbit-example-ddi-client/src/main/java/org/eclipse/hawkbit/ddi/client/ApplicationJsonRequestHeaderInterceptor.java new file mode 100644 index 000000000..680da74f1 --- /dev/null +++ b/examples/hawkbit-example-ddi-client/src/main/java/org/eclipse/hawkbit/ddi/client/ApplicationJsonRequestHeaderInterceptor.java @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * 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 + */ +package org.eclipse.hawkbit.ddi.client; + +import org.springframework.http.MediaType; + +import feign.RequestInterceptor; +import feign.RequestTemplate; + +/** + * An feign request interceptor to set the defined {@code Accept} and + * {@code Content-Type} headers for each request to {@code application/json}. + */ +public class ApplicationJsonRequestHeaderInterceptor implements RequestInterceptor { + + @Override + public void apply(final RequestTemplate template) { + template.header("Accept", MediaType.APPLICATION_JSON_VALUE); + template.header("Content-Type", MediaType.APPLICATION_JSON_VALUE); + } + +} diff --git a/examples/hawkbit-example-ddi-client/src/main/java/org/eclipse/hawkbit/ddi/client/DdiClient.java b/examples/hawkbit-example-ddi-client/src/main/java/org/eclipse/hawkbit/ddi/client/DdiClient.java new file mode 100644 index 000000000..7673f1a41 --- /dev/null +++ b/examples/hawkbit-example-ddi-client/src/main/java/org/eclipse/hawkbit/ddi/client/DdiClient.java @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2011-2016 Bosch Software Innovations GmbH, Germany. All rights reserved. + */ +package org.eclipse.hawkbit.ddi.client; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.hawkbit.ddi.ControllerConstants; +import org.eclipse.hawkbit.ddi.client.resource.RootControllerResourceClient; +import org.eclipse.hawkbit.ddi.model.ControllerBase; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.netflix.feign.support.ResponseEntityDecoder; +import org.springframework.http.ResponseEntity; + +import feign.Feign; +import feign.Logger; +import feign.Logger.Level; +import feign.jackson.JacksonDecoder; +import feign.jackson.JacksonEncoder; + +public class DdiClient { + + @Autowired + private HttpServletRequest request; + + @Autowired + private HttpServletResponse response; + + private final String controllerId; + private final String name; + private final String description; + private RootControllerResourceClient rootControllerResourceClient; + private String rootControllerResourcePath; + + public DdiClient(final String rolloutsUrl, final String controllerId, final String name, final String description, + final String tenant) { + super(); + this.controllerId = controllerId; + this.name = name; + this.description = description; + rootControllerResourcePath = rolloutsUrl + ControllerConstants.BASE_V1_REQUEST_MAPPING; + rootControllerResourcePath = rootControllerResourcePath.replace("{tenant}", tenant); + + createFeignClient(); + } + + private void createFeignClient() { + + // BasicAuthRequestInterceptor TODO + + final Feign.Builder feignBuilder = Feign.builder() + .contract(new IgnoreMultipleConsumersProducersSpringMvcContract()) + .requestInterceptor(new ApplicationJsonRequestHeaderInterceptor()).logLevel(Level.FULL) + .logger(new Logger.ErrorLogger()).encoder(new JacksonEncoder()) + .decoder(new ResponseEntityDecoder(new JacksonDecoder())); + // TODO implement feign client encoder to handle MultiPartFile + // .requestInterceptor(new BasicAuthRequestInterceptor(tenant + "\\" + + // user, password)) + + rootControllerResourceClient = feignBuilder.target(RootControllerResourceClient.class, + rootControllerResourcePath); + + } + + public void startDdiClient() { + + // final HttpServletRequest request; + + // final HttpSession mySession = request.getSession(); + + // final HttpServletRequest request = new; + + final ResponseEntity response = rootControllerResourceClient.getControllerBase("test", request); + final ControllerBase controllerBase = response.getBody(); + + // TODO notify every 10 seconds on the rollout server + + // TODO if new update available -> start download and installation + // process + // report status messages + + } + +} diff --git a/examples/hawkbit-example-ddi-client/src/main/java/org/eclipse/hawkbit/ddi/client/IgnoreMultipleConsumersProducersSpringMvcContract.java b/examples/hawkbit-example-ddi-client/src/main/java/org/eclipse/hawkbit/ddi/client/IgnoreMultipleConsumersProducersSpringMvcContract.java new file mode 100644 index 000000000..c1c24b9ec --- /dev/null +++ b/examples/hawkbit-example-ddi-client/src/main/java/org/eclipse/hawkbit/ddi/client/IgnoreMultipleConsumersProducersSpringMvcContract.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * 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 + */ +package org.eclipse.hawkbit.ddi.client; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cloud.netflix.feign.support.SpringMvcContract; + +import feign.MethodMetadata; + +/** + * Own implementation of the {@link SpringMvcContract} which catches the + * {@link IllegalStateException} which occurs due multiple produces and consumes + * values in the request-mapping + * annoation.https://github.com/spring-cloud/spring-cloud-netflix/issues/808 + */ +public class IgnoreMultipleConsumersProducersSpringMvcContract extends SpringMvcContract { + + private static final Logger LOGGER = LoggerFactory + .getLogger(IgnoreMultipleConsumersProducersSpringMvcContract.class); + + @Override + protected void processAnnotationOnMethod(final MethodMetadata data, final Annotation methodAnnotation, + final Method method) { + try { + super.processAnnotationOnMethod(data, methodAnnotation, method); + } catch (final IllegalStateException e) { + // ignore illegalstateexception here because it's thrown because of + // multiple consumers and produces, see + // https://github.com/spring-cloud/spring-cloud-netflix/issues/808 + LOGGER.trace(e.getMessage(), e); + } + } +} diff --git a/examples/hawkbit-example-ddi-client/src/main/java/org/eclipse/hawkbit/ddi/client/resource/RootControllerResourceClient.java b/examples/hawkbit-example-ddi-client/src/main/java/org/eclipse/hawkbit/ddi/client/resource/RootControllerResourceClient.java new file mode 100644 index 000000000..2d531963f --- /dev/null +++ b/examples/hawkbit-example-ddi-client/src/main/java/org/eclipse/hawkbit/ddi/client/resource/RootControllerResourceClient.java @@ -0,0 +1,15 @@ +/** + * Copyright (c) 2011-2016 Bosch Software Innovations GmbH, Germany. All rights reserved. + */ +package org.eclipse.hawkbit.ddi.client.resource; + +import org.eclipse.hawkbit.ddi.api.RootControllerDdiApi; +import org.springframework.cloud.netflix.feign.FeignClient; + +/** + * Client binding for the Rootcontroller resource of the DDI API. + */ +@FeignClient(url = "${hawkbit.url:localhost:8080}/{tenant}/controller/v1") +public interface RootControllerResourceClient extends RootControllerDdiApi { + +} diff --git a/examples/hawkbit-example-ddi-client/src/main/java/org/hawkbit/example/ddi/client/App.java b/examples/hawkbit-example-ddi-client/src/main/java/org/hawkbit/example/ddi/client/App.java deleted file mode 100644 index cd85d3a8b..000000000 --- a/examples/hawkbit-example-ddi-client/src/main/java/org/hawkbit/example/ddi/client/App.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.hawkbit.example.ddi.client; - -/** - * Hello world! - * - */ -public class App -{ - public static void main( String[] args ) - { - System.out.println( "Hello World!" ); - } -} diff --git a/examples/hawkbit-example-ddi-client/src/test/java/org/eclipse/hawkbit/example/ddi/client/AppTest.java b/examples/hawkbit-example-ddi-client/src/test/java/org/eclipse/hawkbit/example/ddi/client/AppTest.java new file mode 100644 index 000000000..64f34c8ff --- /dev/null +++ b/examples/hawkbit-example-ddi-client/src/test/java/org/eclipse/hawkbit/example/ddi/client/AppTest.java @@ -0,0 +1,18 @@ +package org.eclipse.hawkbit.example.ddi.client; + +import org.eclipse.hawkbit.ddi.client.DdiClient; +import org.junit.Test; + +/** + * Unit test for simple App. + */ +public class AppTest { + + @Test + public void AppTest() { + + final DdiClient ddiClient = new DdiClient("localhost:8080", "mytest", "mytest", "desc", "DEFAULT"); + ddiClient.startDdiClient(); + } + +} diff --git a/examples/hawkbit-example-ddi-client/src/test/java/org/hawkbit/example/ddi/client/AppTest.java b/examples/hawkbit-example-ddi-client/src/test/java/org/hawkbit/example/ddi/client/AppTest.java deleted file mode 100644 index 395886a2d..000000000 --- a/examples/hawkbit-example-ddi-client/src/test/java/org/hawkbit/example/ddi/client/AppTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.hawkbit.example.ddi.client; - -import junit.framework.Test; -import junit.framework.TestCase; -import junit.framework.TestSuite; - -/** - * Unit test for simple App. - */ -public class AppTest - extends TestCase -{ - /** - * Create the test case - * - * @param testName name of the test case - */ - public AppTest( String testName ) - { - super( testName ); - } - - /** - * @return the suite of tests being tested - */ - public static Test suite() - { - return new TestSuite( AppTest.class ); - } - - /** - * Rigourous Test :-) - */ - public void testApp() - { - assertTrue( true ); - } -} diff --git a/hawkbit-ddi-api/src/main/java/org/eclipse/hawkbit/ddi/api/RootControllerDdiApi.java b/hawkbit-ddi-api/src/main/java/org/eclipse/hawkbit/ddi/api/RootControllerDdiApi.java index 8e51e0b86..548f35d72 100644 --- a/hawkbit-ddi-api/src/main/java/org/eclipse/hawkbit/ddi/api/RootControllerDdiApi.java +++ b/hawkbit-ddi-api/src/main/java/org/eclipse/hawkbit/ddi/api/RootControllerDdiApi.java @@ -7,22 +7,15 @@ import java.lang.annotation.Target; import java.util.List; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.validation.Valid; import org.eclipse.hawkbit.ddi.ControllerConstants; -import org.eclipse.hawkbit.ddi.model.ActionFeedback; -import org.eclipse.hawkbit.ddi.model.Artifact; import org.eclipse.hawkbit.ddi.model.Cancel; -import org.eclipse.hawkbit.ddi.model.ConfigData; import org.eclipse.hawkbit.ddi.model.ControllerBase; import org.eclipse.hawkbit.ddi.model.DeploymentBase; import org.hibernate.validator.constraints.NotEmpty; -import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; @@ -33,6 +26,21 @@ import org.springframework.web.bind.annotation.RequestParam; @RequestMapping(ControllerConstants.BASE_V1_REQUEST_MAPPING) public interface RootControllerDdiApi { + /** + * Root resource for an individual {@link Target}. + * + * @param targetid + * of the {@link Target} that matches to + * {@link Target#getControllerId()} + * @param request + * the HTTP request injected by spring + * @return the response + */ + @RequestMapping(method = RequestMethod.GET, value = "/{targetid}", produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity getControllerBase(@PathVariable("targetid") final String targetid, + final HttpServletRequest request); + /** * Returns all artifacts of a given software module and target. * @@ -49,21 +57,6 @@ public interface RootControllerDdiApi { @PathVariable("targetid") final String targetid, @PathVariable("softwareModuleId") final Long softwareModuleId); - /** - * Root resource for an individual {@link Target}. - * - * @param targetid - * of the {@link Target} that matches to - * {@link Target#getControllerId()} - * @param request - * the HTTP request injected by spring - * @return the response - */ - @RequestMapping(method = RequestMethod.GET, value = "/{targetid}", produces = { "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }) - public ResponseEntity getControllerBase(@PathVariable("targetid") final String targetid, - final HttpServletRequest request); - /** * Handles GET {@link Artifact} download request. This could be full or * partial (as specified by RFC7233 (Range Requests)) download request. @@ -83,11 +76,14 @@ public interface RootControllerDdiApi { * {@link HttpStatus#OK} or in case of partial download * {@link HttpStatus#PARTIAL_CONTENT}. */ - @RequestMapping(method = RequestMethod.GET, value = "/{targetid}/softwaremodules/{softwareModuleId}/artifacts/{fileName}") - public ResponseEntity downloadArtifact(@PathVariable("targetid") final String targetid, - @PathVariable("softwareModuleId") final Long softwareModuleId, - @PathVariable("fileName") final String fileName, final HttpServletResponse response, - final HttpServletRequest request); + // @RequestMapping(method = RequestMethod.GET, value = + // "/{targetid}/softwaremodules/{softwareModuleId}/artifacts/{fileName}") + // public ResponseEntity downloadArtifact(@PathVariable("targetid") + // final String targetid, + // @PathVariable("softwareModuleId") final Long softwareModuleId, + // @PathVariable("fileName") final String fileName, final + // HttpServletResponse response, + // final HttpServletRequest request); /** * Handles GET {@link Artifact} MD5 checksum file download request. @@ -106,12 +102,16 @@ public interface RootControllerDdiApi { * @return {@link ResponseEntity} with status {@link HttpStatus#OK} if * successful */ - @RequestMapping(method = RequestMethod.GET, value = "/{targetid}/softwaremodules/{softwareModuleId}/artifacts/{fileName}" - + ControllerConstants.ARTIFACT_MD5_DWNL_SUFFIX, produces = MediaType.TEXT_PLAIN_VALUE) - public ResponseEntity downloadArtifactMd5(@PathVariable("targetid") final String targetid, - @PathVariable("softwareModuleId") final Long softwareModuleId, - @PathVariable("fileName") final String fileName, final HttpServletResponse response, - final HttpServletRequest request); + // @RequestMapping(method = RequestMethod.GET, value = + // "/{targetid}/softwaremodules/{softwareModuleId}/artifacts/{fileName}" + // + ControllerConstants.ARTIFACT_MD5_DWNL_SUFFIX, produces = + // MediaType.TEXT_PLAIN_VALUE) + // public ResponseEntity downloadArtifactMd5(@PathVariable("targetid") + // final String targetid, + // @PathVariable("softwareModuleId") final Long softwareModuleId, + // @PathVariable("fileName") final String fileName, final + // HttpServletResponse response, + // final HttpServletRequest request); /** * Resource for {@link SoftwareModule} {@link UpdateAction}s. @@ -153,11 +153,15 @@ public interface RootControllerDdiApi { * * @return the response */ - @RequestMapping(value = "/{targetid}/" + ControllerConstants.DEPLOYMENT_BASE_ACTION + "/{actionId}/" - + ControllerConstants.FEEDBACK, method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity postBasedeploymentActionFeedback(@Valid @RequestBody final ActionFeedback feedback, - @PathVariable("targetid") final String targetid, @PathVariable("actionId") @NotEmpty final Long actionId, - final HttpServletRequest request); + // @RequestMapping(value = "/{targetid}/" + + // ControllerConstants.DEPLOYMENT_BASE_ACTION + "/{actionId}/" + // + ControllerConstants.FEEDBACK, method = RequestMethod.POST, consumes = + // MediaType.APPLICATION_JSON_VALUE) + // public ResponseEntity postBasedeploymentActionFeedback(@Valid + // @RequestBody final ActionFeedback feedback, + // @PathVariable("targetid") final String targetid, + // @PathVariable("actionId") @NotEmpty final Long actionId, + // final HttpServletRequest request); /** * This is the feedback channel for the config data action. @@ -171,10 +175,13 @@ public interface RootControllerDdiApi { * * @return status of the request */ - @RequestMapping(value = "/{targetid}/" - + ControllerConstants.CONFIG_DATA_ACTION, method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity putConfigData(@Valid @RequestBody final ConfigData configData, - @PathVariable("targetid") final String targetid, final HttpServletRequest request); + // @RequestMapping(value = "/{targetid}/" + // + ControllerConstants.CONFIG_DATA_ACTION, method = RequestMethod.PUT, + // consumes = MediaType.APPLICATION_JSON_VALUE) + // public ResponseEntity putConfigData(@Valid @RequestBody final + // ConfigData configData, + // @PathVariable("targetid") final String targetid, final HttpServletRequest + // request); /** * {@link RequestMethod.GET} method for the {@link Cancel} action. @@ -209,10 +216,14 @@ public interface RootControllerDdiApi { * @return the {@link ActionFeedback} response */ - @RequestMapping(value = "/{targetid}/" + ControllerConstants.CANCEL_ACTION + "/{actionId}/" - + ControllerConstants.FEEDBACK, method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity postCancelActionFeedback(@Valid @RequestBody final ActionFeedback feedback, - @PathVariable("targetid") @NotEmpty final String targetid, - @PathVariable("actionId") @NotEmpty final Long actionId, final HttpServletRequest request); + // @RequestMapping(value = "/{targetid}/" + + // ControllerConstants.CANCEL_ACTION + "/{actionId}/" + // + ControllerConstants.FEEDBACK, method = RequestMethod.POST, consumes = + // MediaType.APPLICATION_JSON_VALUE) + // public ResponseEntity postCancelActionFeedback(@Valid @RequestBody + // final ActionFeedback feedback, + // @PathVariable("targetid") @NotEmpty final String targetid, + // @PathVariable("actionId") @NotEmpty final Long actionId, final + // HttpServletRequest request); }