Just need to check the feasibility if we can deploy our own custom rest method into apache ignite service grid which can be accessed by clients.
Idea is to get the request into my custom rest method exposed, make the relevant changes and send back the response.
Thanks,
It's not trivial to add a method to the existing Ignite REST server but it's fairly straight-forward to have your own end-point running using the Service Grid.
This is my aproach using Ignite service proxy (don't forget disable ignite embedded http or you have to exclude some dependencies):
RESTServiceImpl.java:
public class RESTServiceImpl implements RESTService {
#IgniteInstanceResource
private Ignite ignite;
#LoggerResource
private IgniteLogger log;
private Server jettyServer;
public void init(ServiceContext ctx) {
ServletContextHandler context = new
ServletContextHandler(ServletContextHandler.NO_SESSIONS);
context.setContextPath("/api");
jettyServer = new Server(8888);
jettyServer.setHandler(context);
ServletHolder jerseyServlet = context.addServlet(ServletContainer.class, "/*");
jerseyServlet.setInitOrder(0);
// Tells the Jersey Servlet which REST service/class to load.
jerseyServlet.setInitParameter("jersey.config.server.provider.packages", "services.rest");
}
public void execute(ServiceContext ctx) {
log.info("Starting REST Service");
try {
jettyServer.start();
} catch (Exception e) {
e.printStackTrace();
}
log.info("Starting REST Service : OK");
}
public void cancel(ServiceContext ctx) {
log.info("Stopping REST Service on node:" + ignite.cluster().localNode());
try {
jettyServer.stop();
} catch (Exception e) {
e.printStackTrace();
}
RESTController.java:
#Path("/mypath")
public class EventCaseController {
private Ignite ignite = Ignition.ignite();
private RESTService restService = ignite.services().serviceProxy(RESTService.SERVICE_NAME,
RESTService.class, false);
#GET
#Path("size")
#Produces(MediaType.APPLICATION_JSON)
public JSONObject eventCaseCacheSize() {
// some logics
}
}
Related
I am new to Web Service development, currently building a SOAP Web Service with Spring Boot 2.7.0, Java 17.
As well as a client application that communicates with this soap service via JMS.
But I do not know the procedure of the process.
The way I see it -> The client application (Producer) sends a message to a queue that lives on the server side (Consumer), the queue pops the message when ready to consume and redirects it to the endpoint handler method and then sends a response in the response queue back to the client side.
However, I don't know how to redirect the JMS message to the endpoint. Nor do I know how to send it back. I have read all of the documentations related to "SOAP over JMS", CXF-SOAP-JMS", "ActiveMQ with Spring", etc... None of them helped me fix this problem.
Using SOAP with http is pretty easy by exploiting the "WebServiceTemplate", provided by Spring-WS API. But when I tried using it over JMS I encountered several problems, including the following:
What to do with the JMS Message once in the destination object?
How do I send it specifically to my endpoint handler method?
What and how do I send back to the response destination?
Sample code of what I've tried latest
CLIENT APP
Client Configuration
#Configuration
public class SoapClientConfiguration {
#Value("${spring.activemq.broker-url}")
private String activeMqUrl;
#Value("${spring.activemq.user}")
private String userName;
#Value("${spring.activemq.password}")
private String password;
#Bean
Jaxb2Marshaller jaxb2Marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setPackagesToScan("com.mile.soap.client.app.quiz");
return marshaller;
}
#Bean
WebServiceTemplate template() {
return new WebServiceTemplate(jaxb2Marshaller());
}
#Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(mqConnectionFactory());
return factory;
}
#Bean
public SingleConnectionFactory mqConnectionFactory(){
SingleConnectionFactory factory = new SingleConnectionFactory();
ActiveMQConnectionFactory mqConnectionFactory = new ActiveMQConnectionFactory();
mqConnectionFactory.setBrokerURL(activeMqUrl);
mqConnectionFactory.setUserName(userName);
mqConnectionFactory.setPassword(password);
factory.setTargetConnectionFactory(mqConnectionFactory);
return factory;
}
#Bean
public JmsTemplate jmsTemplate(){
JmsTemplate template = new JmsTemplate();
template.setConnectionFactory(mqConnectionFactory());
return template;
}
Client Service
#Service
public class SoapClient extends WebServiceGatewaySupport{
#Autowired WebServiceTemplate template;
#Autowired JmsTemplate jmsTemplate;
public CategoriesResponse getCategories() {
CategoriesResponse response = new CategoriesResponse();
try {
SAAJResult soapRequest = new SAAJResult();
template.getMarshaller().marshal(new GetCategoriesRequest(), soapRequest);
Message m = jmsTemplate.sendAndReceive("example.queue", new MessageCreator() {
#Override
public Message createMessage(Session session) throws JMSException {
return session.createObjectMessage(soapRequest.toString());
}
});
response = m.getBody(CategoriesResponse.class);
}
catch (Exception e) {
e.printStackTrace();
}
return response;
}
SERVER SIDE APP
ActiveMQ Configuration
#Configuration #EnableJms
public class ActiveMqConfig {
#Value("${spring.activemq.broker-url}")
private String activeMqUrl;
#Value("${spring.activemq.user}")
private String userName;
#Value("${spring.activemq.password}")
private String password;
#Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(mqConnectionFactory());
return factory;
}
#Bean
public SingleConnectionFactory mqConnectionFactory(){
SingleConnectionFactory factory = new SingleConnectionFactory();
ActiveMQConnectionFactory mqConnectionFactory = new ActiveMQConnectionFactory();
mqConnectionFactory.setBrokerURL(activeMqUrl);
mqConnectionFactory.setUserName(userName);
mqConnectionFactory.setPassword(password);
factory.setTargetConnectionFactory(mqConnectionFactory);
return factory;
}
}
Main Configuration (WSDL/SERVLET)
#Configuration
#EnableWs
public class SoapConfiguration extends WsConfigurerAdapter{
#Bean(name = Bus.DEFAULT_BUS_ID)
public SpringBus springBus(){
SpringBus bus = new SpringBus();
return bus;
}
#Bean
public ServletRegistrationBean<MessageDispatcherServlet> messageDispatcherServlet(
ApplicationContext applicationContext, SpringBus springBus){
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean<>(servlet, "/*");
}
//wsdl
#Bean(name = "quiz") #SneakyThrows
public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema schema) {
DefaultWsdl11Definition defaultWsdl11Definition = new DefaultWsdl11Definition();
defaultWsdl11Definition.setPortTypeName("QuizMainEndPoint");
defaultWsdl11Definition.setLocationUri("/");
defaultWsdl11Definition.setTargetNamespace("http://www.mile.com/collection/management/soap/Quiz");
defaultWsdl11Definition.setTransportUri("http://www.openuri.org/2002/04/soap/jms/");
defaultWsdl11Definition.setSchema(schema);
return defaultWsdl11Definition;
}
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
EndpointInterceptor endpointInterceptor = new PayloadRootSmartSoapEndpointInterceptor(
new QuizMainEndpointInterceptor(), "http://www.mile.com/collection/management/soap/Quiz", "GetCategoriesRequest");
interceptors.add(endpointInterceptor);
}
#Bean
public XsdSchema schema() {
return new SimpleXsdSchema(new ClassPathResource("/schemas/QuizSchema/quiz.xsd"));
}
}
Listener
#Component
public class Listener {
#JmsListener(destination = "example.queue")
public void listenRequests(Message message) {
System.out.println(message.toString());
/*I RECEIVE THE MESSAGE BUT I HAVE NO IDEA WHAT TO DO WITH IT.
* HOW DO I CONSUME IT?
*/
}
}
Method in a class annotated with #Endpoint
#ResponsePayload
#PayloadRoot(namespace = NAMESPACE, localPart = "GetCategoriesRequest")
public CategoriesResponse getCategories( #RequestPayload GetCategoriesRequest request) {
CategoriesResponse response = new CategoriesResponse(service.getCategories());
/*
* How to CONVERT my JMS Message living in the Destination Object - "example.queue" To a SOAP Message
* and be RECOGNISED by this exact method??
Do i send a JMS response here or somewhere else?
Is it sent by default?
*/
return response;
}
Thank you for reading thoroughly. I'd appreciate any kind of help.
I'm trying to consume a rest web service which is authenticated in spring boot application where the Httpmethod is POST,
Below I would like to show how all set up work to consume authenticated web service for HttpMethod.GET and then what changes I try to consume same authenticated web service for HttpMethod.POST and throws 401 Unauthorized ERROR,
RestTemplateFactory to get restTemplte,
public class RestTemplateFactory implements FactoryBean<RestTemplate>, InitializingBean {
#Autowired
private RestTemplate restTemplate;
public RestTemplate getObject() {
return restTemplate;
}
public Class<RestTemplate> getObjectType() {
return RestTemplate.class;
}
public boolean isSingleton() {
return true;
}
public void afterPropertiesSet() {
HttpHost host = new HttpHost("localhost", 9090, "http");
restTemplate = new RestTemplate(
new HttpComponentsClientHttpRequestFactoryBasicAuth(host));
}
}
For basic authentication,
public class HttpComponentsClientHttpRequestFactoryBasicAuth extends HttpComponentsClientHttpRequestFactory {
HttpHost host;
public HttpComponentsClientHttpRequestFactoryBasicAuth(HttpHost host) {
super();
this.host = host;
}
protected HttpContext createHttpContext(HttpMethod httpMethod, URI uri) {
return createHttpContext();
}
private HttpContext createHttpContext() {
AuthCache authCache = new BasicAuthCache();
BasicScheme basicAuth = new BasicScheme();
authCache.put(host, basicAuth);
BasicHttpContext localcontext = new BasicHttpContext();
localcontext.setAttribute(HttpClientContext.AUTH_CACHE, authCache);
return localcontext;
}
}
Calling a authenticated web service for HttpMethod.Get method,
public ResponseEntity<SomeResponse> consumeRestApi(SomeRequest request) throws InvalidDataException {
ResponseEntity<SomeResponse> responseEntity = null;
try {
RestTemplate restTemplate = restTemplateFactory.getRestTemplate();
restTemplate
.getInterceptors()
.add(new BasicAuthorizationInterceptor(username, pwd));
responseEntity = restTemplate.exchange("http://localhost:9090/sendMail?phone=60598745&email=abc#gmail.com", HttpMethod.GET, null, SomeResponse.class);
} catch (Exception e) {
// Exception handing...
}
return responseEntity;
}
And I do have dummy server running at localhost, with HttpMethod.Get and this is the authenticated service I'm trying to consume in above set up,
#RequestMapping(value = "/sendMail", method = RequestMethod.GET)
public ResponseEntity<SomeResponse> sendmail(#RequestParam String phone, #RequestParam String email){
SomeResponse response = SomeResponse.builder()
.id("101")
.type("formdata")
.fieldValues(getFieldValues(phone,email))
.build();
return new ResponseEntity<>(response,HttpStatus.CREATED);
}
When its a HttpMethod.GET method it works perfectly fine with all the set up mentioned above,
Now, I want to change the same web service to be consumed, to accept a HttpMethod.POST
So below are the changes I tried out but it throw back an error of 401 i.e. Unauthorized error
The changes I try for post method,
By keeping the RestTemplateFactory and HttpComponentsClientHttpRequestFactoryBasicAuth same
I first change, the rest api on dummy server to accept request with POST so,
#RequestMapping(value = "/sendMail", method = RequestMethod.POST)
public ResponseEntity<SomeResponse> sendmail(#RequestParam String phone, #RequestParam String email){
// Same as above
}
Next change is calling method with method post,
public ResponseEntity<SomeResponse> consumeRestApi(SomeRequest request) throws InvalidDataException {
ResponseEntity<SomeResponse> responseEntity = null;
try {
RestTemplate restTemplate = restTemplateFactory.getRestTemplate();
restTemplate
.getInterceptors()
.add(new BasicAuthorizationInterceptor(username, pwd));
SomeResponse response = restTemplate.postForObject("http://localhost:9090/sendMail?phone=60598745&email=abc#gmail.com",request, SomeResponse.class);
} catch (Exception e) {
// Exception handling....
}
}
return new ResponseEntity<SomeResponse>(HttpStatus.CREATED);
}
Does anyone has any suggestion where I'm going wrong with this,
Thanks in advance.
I guess the issue lies in your way of creating and configuring the RestTemplate. Spring Boot provides the RestTemplateBuilder to construct a RestTemplate and it has builder methods to do additional configuration.
In addition the RestTemplate is thread safe so instead of re-recreating it to use it you can reuse the created instance. That being said your calling class can be refactored to something like this
public class EndpointTester {
private final RestTemplate rest;
public EndpointTester(RestTemplateBuilder rtb, String username, String pwd) {
this.rest = rtb.basicAuthorization(username, pwd).build();
}
public ResponseEntity<SomeResponse> consumeRestApi(SomeRequest request) throws InvalidDataException {
ResponseEntity<SomeResponse> responseEntity = null;
try {
responseEntity = rest.postForEntity("http://localhost:9090/sendMail?phone=60598745&email=abc#gmail.com", null, SomeResponse.class);
} catch (Exception e) {
// Exception handing...
}
return responseEntity;
}
}
This way you don't need your RestTemplateFactory and HttpComponentsClientHttpRequestFactoryBasicAuth which simplifies your configuration and code.
I am trying to create a new queue in RabbitMQ using Spring AMQP on server startup of my web application. I am not getting the exact configuration code how to achieve it.
Below is my code snippet. Please correct the following.
#Configuration
public class RabbitMQConfiguration {
#Bean
public ConnectionFactory rabbitConnectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory("10.165.18.29");
connectionFactory.setUsername("User");
connectionFactory.setPassword("user");
return connectionFactory;
}
#Bean
public SimpleMessageListenerContainer messageListenerContainer() {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(rabbitConnectionFactory());
container.addQueueNames("create.queue");
container.setMessageListener(exampleListener());
return container;
}
#Bean
public MessageListener exampleListener() {
return new MessageListener() {
public void onMessage(Message message) {
System.out.println("received: " + message);
}
};
}
}
See the documentation.
Simply add <rabbit:queue ... /> beans and a <rabbit:admin ... /> and the admin will automatically declare the queues when the connection is first established.
I've created a Rest service with four methods, GET,POST,UPDATE and DELETE.
These methods make connections to a Database to retrieve and store data.
Now I want to test each method. I've used the Jersey Test Framework for this. And it is working as long as I remove the code what actually makes the call to the database. When I leave the code that makes the call to the database it throws an exception that it could not connect to the database.
EDIT: I have done some research and used dependancy injection. The db calls are moved to a separate class but I'm still doing something wrong.
DatabaseResults. In this class the call to the DB is made.
public class DatabaseResults {
private final String getQuery = "SELECT * FROM movies";
private Connection connection = null;
private PreparedStatement pstmt = null;
private final ArrayList<Movie> jsonList = new ArrayList<>();
public JSONObject getAllMovies() throws SQLException {
try {
ComboPooledDataSource dataSource = DatabaseUtility.getDataSource();
connection = dataSource.getConnection();
pstmt = connection.prepareStatement(getQuery);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
jsonList.add(new Movie(rs.getString(1), rs.getString(2), rs.getString(4), rs.getString(3)));
}
} catch (SQLException ex) {
System.out.println(ex);
System.out.println("Could not retrieve a connection");
connection.rollback();
} finally {
connection.close();
}
JSONObject jsonObject = new JSONObject();
jsonObject.put("movies", jsonList);
return jsonObject;
}
}
MoviesResource that contains the REST methods
#Path("movies")
public class MoviesResource {
....
private DatabaseResults dbResults = null;
public MoviesResource() {
this(new DatabaseResults());
}
MoviesResource(DatabaseResults dbr){
this.dbResults = dbr;
}
....
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response getAllMovies() throws JSONException, SQLException {
return Response.status(200).entity(dbResults.getAllMovies().toString()).build();
}
The Test class
#RunWith(MockitoJUnit44Runner.class)
public class MovieResourceTest extends JerseyTest {
JSONObject jsonObject = new JSONObject();
#Mock
DatabaseResults dbr;
#Before
public void setup() throws SQLException{
jsonObject.put("id", "hello");
when(dbr.getAllMovies()).thenReturn(jsonObject);
}
Client client = ClientBuilder.newClient();
WebTarget target = client
.target("http://localhost:9998/RestServiceMovies/resources");
#Override
protected Application configure() {
return new ResourceConfig(MoviesResource.class);
}
#Test
public void getAllMoviesTest() throws SQLException {
String responseGetAllMovies = target("/movies").request().get(String.class);
Assert.assertTrue("hello".equals(responseGetAllMovies));
}
At this moment I can run the tests but still when I test the getAllMovies() method it makes a call to the real database instead of returning the jsonObject.
I have the feeling that a connection is missing between the mock object and the constructor from the MovieResource class?
When you register your resource as a class
new ResourceConfig(MoviesResource.class)
you are telling Jersey to create the instance. If you don't have any DI configured, it will just call the no-arg constructor. In your no-arg constructor, you are just creating the service yourself. It knows nothing about your mock.
What you should do instead is register the resource class as an instance. That way you can pass the mock to the constructor.
MockitoAnnotations.initMocks(this);
return new ResourceConfig()
.register(new MoviesResource(dbr));
Don't use the Mockito runner. Instead use the MockitoAnnotations.initMocks method. That way you control when the #Mocks are injected. If you use the runner, the injection will not happen in time, as the the configure method is called by the framework before the Mockito injection happens.
Within a Unit/Integration Test, I'm trying to use the RESTEasy embedded server TJWSEmbeddedJaxrsServer or POJOResourceFactory inorder to simulate through a MockHttpRequest.get("/data") a resource call for test purpose.
My problem is that based on the use of the server or the Resource factory I'm not able to have a non null instance of spring beans which are injected normally within my resources.
Here's some code for clarification, thanks in advance.
Spring application context :
<context:annotation-config />
<context:component-scan base-package="com.cdcfast.service" />
<bean id="simpleResource" class="com.cdcfast.rest.SimpleResource" />
SimpleResource.java :
#Component
#Path("/data")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class SimpleResource {
#Autowired
private SimpleService service;
#GET
#Produces(MediaType.APPLICATION_JSON)
public List<Data> getData() {
return MockDataBase.getInstance().getRows();
}
Unit Test :
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath*:/test/spring/testApplicationContext.xml" })
public class FakeTest {
private Dispatcher dispatcher;
#Before
public void before() {
dispatcher = MockDispatcherFactory.createDispatcher();
POJOResourceFactory noDefaults = new POJOResourceFactory(SimpleResource.class);
dispatcher.getRegistry().addResourceFactory(noDefaults);
}
#Test
public void aTestThatAlwaysPass() throws URISyntaxException {
MockHttpRequest request = MockHttpRequest.get("/data");
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
Assertions.assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
Assertions.assertThat(response.getContentAsString()).isNotNull().isNotEmpty();
}
}
I've had this before because the RESTEasy factories create the POJO rather than Spring so they don't get wired up which can be worked around in the full container but is less easy in a test. The best way around this is to get a handle to your POJO once the factory creates it and then do something similar to this:
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(myPojo);
I personally ended up having Spring create the RESTEasy beans using the RESTEasy-Spring plugin and then launching my tests using Jetty, not sure if that is an option for you though.
I exeprienced same problem and i'have solved in similar way as James did:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:spring-context-test.xml" })
public class TestMyService {
Dispatcher dispatcher;
private String username = "user";
#Autowired
ApplicationContext context;
#Before
public void setUp() {
MyService g = new MyService(); //rest service with #autowired spring beans
context.getAutowireCapableBeanFactory().autowireBean(g);
dispatcher = MockDispatcherFactory.createDispatcher();
dispatcher.getRegistry().addSingletonResource(g);
}
#Test
public void TestRest() {
MockHttpRequest request;
try {
request = MockHttpRequest.get("/rest/service").header("LOGON_USER", username);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertTrue("Error, unexpected status code: " + response.getStatus(), response.getStatus() == 200);
LoggerFactory.getLogger(this.getClass()).info("********** " + response.getContentAsString());
} catch (URISyntaxException e) {
Log.error(e.getMessage(), e);
fail(e.getMessage());
}
}
}