I am trying to create 2 Azure Functions with Spring Cloud but I can't make it work.
#Configuration
public class FirstFunction extends AzureSpringBootRequestHandler<Optional<Void>, String>
{
#FunctionName("firstFunction")
public void run(
#HttpTrigger(name = "req", methods = {HttpMethod.POST}, authLevel = AuthorizationLevel.FUNCTION) HttpRequestMessage<Optional<String>> request,
final ExecutionContext context)
{
handleRequest(Optional.empty(), context);
}
#Bean
#Lazy
Function<Optional<Void>, String> firstFunction()
{
return context ->
{
// do firstFunction stuff;
};
}
}
#Configuration
public class SecondFunction extends AzureSpringBootRequestHandler<Optional<Void>, String>
{
#FunctionName("secondFunction")
public void run(
#HttpTrigger(name = "req", methods = {HttpMethod.POST}, authLevel = AuthorizationLevel.FUNCTION) HttpRequestMessage<Optional<String>> request,
final ExecutionContext context)
{
handleRequest(Optional.empty(), context);
}
#Bean
#Lazy
Function<Optional<Void>, String> secondFunction()
{
return context ->
{
// do secondFunction stuff;
};
}
}
#SpringBootApplication
public class Application
{
public static void main(final String[] args)
{
SpringApplication.run(Application.class, args);
}
}
Using the above code with the dependency on spring-cloud-function-dependencies 2.0.1.RELEASE, it always hits the firstFunction Bean when calling both the firstFunction and secondFunction endpoints.
After doing some googling, I found this SO answer that suggests to move to 2.1.
However when I tried changing to 2.1.1.RELEASE, I am getting an exception where it is failing to find a main class:
System.Private.CoreLib: Exception while executing function: Functions.extractContent. System.Private.CoreLib: Result: Failure
Exception: IllegalArgumentException: Failed to locate main class
Stack: java.lang.IllegalStateException: Failed to discover main class. An attempt was made to discover main class as 'MAIN_CLASS' environment variable, system property as well as entry
in META-INF/MANIFEST.MF (in that order).
Need some help on what I am doing wrong.
I tested at my side, and everything was OK.
You may get my demo at: https://github.com/AI-EVO/azuresptingfunction.git . The project is based on the official demo: https://github.com/Azure-Samples/hello-spring-function-azure
My changes:
HelloFunction.java
#SpringBootApplication
public class HelloFunction {
public static void main(String[] args) throws Exception {
SpringApplication.run(HelloFunction.class, args);
}
#Bean("hello")
public Function<User, Greeting> hello() {
return user -> new Greeting("Hello! Welcome, " + user.getName());
}
#Bean("hi")
public Function<User, Greeting> hi() {
return user -> new Greeting("Hi! Welcome, " + user.getName());
}
}
Modify HelloHandler.java
public class HelloHandler extends AzureSpringBootRequestHandler<User, Greeting> {
#FunctionName("hello")
public Greeting execute(
#HttpTrigger(name = "request", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<User>> request,
ExecutionContext context) {
context.getLogger().info("Greeting user name: " + request.getBody().get().getName());
return handleRequest(request.getBody().get(), context);
}
}
Add HiHandler.java
public class HiHandler extends AzureSpringBootRequestHandler<User, Greeting> {
#FunctionName("hi")
public Greeting execute(#HttpTrigger(name = "request", methods = { HttpMethod.GET,
HttpMethod.POST }, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<User>> request,
ExecutionContext context) {
context.getLogger().info("Greeting user name: " + request.getBody().get().getName());
return handleRequest(request.getBody().get(), context);
}
}
Run functions:
mvn azure-functions:run
Test with postman
From function hello:
From function hi:
Related
I have a small vertx application with an AppLauncher class that extend of VertxCommandLauncher and I set a appConfig.json with the typical config parameters :
public class AppLauncher extends VertxCommandLauncher implements VertxLifecycleHooks {
public static void main(String[] args) {
new AppLauncher().dispatch(args);
}
#Override
public void afterConfigParsed(JsonObject config) {
AppConfig.INSTANCE.setConfig(config);
}
To run my application in my IDE I put in edit configuration my main class (Applauncher.java) and the arguments :
run io.vertx.covid.verticle.MainVerticle -conf../vertx-application/src/main/resources/appConfig.json
This is my test class:
#BeforeAll
static void deployVerticles(Vertx vertx, VertxTestContext testContext) {
vertx.deployVerticle(BaseVerticle.class.getName(),testContext
.succeeding(id->testContext.completeNow()));
}
This is my BaseVerticle class that all my verticles extends from:
public abstract class BaseVerticle extends AbstractVerticle {
public static String CONTEXT_PATH = AppConfig.INSTANCE.getConfig().getString(Constants.CONTEXT_PATH);
}
And this is my AppConfig class :
public enum AppConfig {
INSTANCE;
private JsonObject config;
public JsonObject getConfig() {
return config;
}
public void setConfig(JsonObject config) {
this.config = config;
}
}
Everything works, but if I would like to test it in a separete way then I deploy my verticles but I have a Nullpointer in the CONTEXT_PATH (BaseVerticle class) because the config (suppose to be taken from appConfig.json) is null.
I haven't found a way to pass the arguments with my appConfig.json or should I call to the main method passing the arguments?
I like to do something that is similar to profiles in my vertx application.
If you set an environment variable with the key vertx-config-path before the vertx instance is initialized, you can control where vertx's config retriever (you might need to add vert-config to your gradle/maven dependencies) gets the configuration from.
In your launcher, you can do something like the following, which will give you the ability to add profile based config files to your resources folder conf/config-%s.json where %s is the profile name:
public class CustomLauncher extends Launcher {
public static final String ACTIVE_PROFILE_PROPERTY = "APP_ACTIVE_PROFILE";
private static final CLI cli = CLI.create("main")
.addOption(new Option()
.setShortName("p")
.setLongName("profile")
);
public static void main(String[] args) {
initDefaults(Arrays.asList(args));
new CustomLauncher().dispatch(args);
}
public static void executeCommand(String cmd, String... args) {
initDefaults(Arrays.asList(args));
new CustomLauncher().execute(cmd, args);
}
public static void initDefaults(List<String> args) {
System.setProperty(LoggerFactory.LOGGER_DELEGATE_FACTORY_CLASS_NAME, SLF4JLogDelegateFactory.class.getName());
CommandLine parse = cli.parse(args);
String profile = parse.getOptionValue("p");
if (profile != null && !profile.isEmpty()) {
System.setProperty(ACTIVE_PROFILE_PROPERTY, profile);
System.setProperty("vertx-config-path", String.format("conf/config-%s.json", profile));
}
}
}
Then in your test, instead of relaying on vertx test extension to inject vertx for you, you can initialize it by yourself and control the profile (aka which config file to load) like the following:
private static Vertx vertx;
#BeforeAll
public static void deployVerticles(VertxTestContext testContext) {
CustomLauncher.initDefaults(Arrays.asList("--profile", "test"))
vertx = Vertx.vertx();
ConfigRetriever.create(vertx).getConfig(asyncResult -> {
if (asyncResult.succeeded()) {
JsonObject config = asyncResult.result();
DeploymentOptions deploymentOptions = new DeploymentOptions()
.setConfig(config);
vertx.deployVerticle(BaseVerticle.class.getName(), deploymentOptions);
} else {
// handle failure
}
});
}
Then when you run your application, instead of providing -conf, you can use -p or --profile
I also highly recommend to get familiar with vertx-config as you can also get env variables, k8s config maps, and much more.
EDIT: I also highly recommend to move to Kotlin if possible, makes the async-code much easier to handle in an imperative way (with Coroutines). It's very hard to deal with libraries like Vert.x in Java compared to languages like Kotlin.
I solved my problem creating a verticle with the config stuffs (vertx-config documentation), here is my verticle config class:
public class ConfigVerticle extends AbstractVerticle {
protected static Logger logger = LoggerFactory.getLogger(ConfigVerticle.class);
public static JsonObject config;
#Override
public void start() throws Exception {
ConfigStoreOptions fileStore = new ConfigStoreOptions()
.setType("file")
.setOptional(true)
.setConfig(new JsonObject().put("path", "conf/appConfig.json"));
ConfigStoreOptions sysPropsStore = new ConfigStoreOptions().setType("sys");
ConfigRetrieverOptions options = new ConfigRetrieverOptions().addStore(fileStore).addStore(sysPropsStore);
ConfigRetriever retriever = ConfigRetriever.create(vertx, options);
retriever.getConfig(ar -> {
if (ar.failed()) {
logger.info("Failed to retrieve config from appConfig.json");
} else {
config = ar.result();
vertx.deployVerticle(MainVerticle.class.getName(), new DeploymentOptions().setConfig(config));
}
});
}
}
And my MainVerticle.class I pass the new configuration like this:
public class MainVerticle extends AbstractVerticle {
#Override
public void start(){
vertx.deployVerticle(BackendVerticle.class.getName(), new DeploymentOptions().setConfig(config()));
}
}
Then, my simpleTests :
#ExtendWith(VertxExtension.class)
public class BaseCovidTest {
protected WebClient webClient;
#BeforeEach
void initWebClient(Vertx vertx){
webClient = WebClient.create(vertx);
}
#BeforeAll
static void deployVerticles(Vertx vertx, VertxTestContext vertxTestContext) {
vertx.deployVerticle(ConfigVerticle.class.getName() ,vertxTestContext
.succeeding(id-> {
try {
vertxTestContext.awaitCompletion(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
vertxTestContext.completeNow();
}));
}
}
And everything works, thanks #Tom that inspired me to fix it!
I'm using the latest Springframework, and having issues trying to GET an int from my server. All code was writen in Java.
When I interact with the server throught browser everything is OK. And when interacting with the server through the client I'm getting a NullPointerException.
Keep in mind I am a beginner software student.
Server Code (I tried both, works fine when using browser):
public class RestController {
private GameSession gameSession = new GameSession();
#RequestMapping(value = "registerPlayer")
public int registerPlayer(#RequestParam("name") String name, #RequestParam("mode") boolean mode) {
return gameSession.registerPlayer(name, mode);
}
#RequestMapping(value = "registerPlayer/{name}/{mode}")
public int registerPlayer(#PathVariable String name, #PathVariable boolean mode) {
return gameSession.registerPlayer(name, mode);
}
}
Client Code (again tried both, with the same result):
#Component
public class GameSessionClient implements ISeaBattleGame{
#Autowired
private RestOperations restOperations;
private String url;
#Override
public int registerPlayer(String name, boolean singlePlayerMode) {
url = "http://localhost:8080/" + "registerPlayer?name=" + name + "&mode=" + (singlePlayerMode ? 1 : 0);
return restOperations.getForObject(url, int.class);
}
#Override
public int registerPlayer(String name, boolean singlePlayerMode) {
url = "http://localhost:8080/" + "registerPlayer/" + name + "/" + (singlePlayerMode ? 1 : 0);
return restOperations.getForObject(url, int.class);
}
}
RestConfig Code:
#Configuration
public class RestConfig {
#Bean
public RestOperations createRestTemplate(final ClientHttpRequestFactory clientHttpRequestFactory){
return new RestTemplate(clientHttpRequestFactory);
}
#Bean
public ClientHttpRequestFactory clientHttpRequestFactory(#Value("${connect.timeout}") final int connectTimeout, #Value("${read.timeout}") final int readTimeout){
HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
httpComponentsClientHttpRequestFactory.setReadTimeout(readTimeout);
httpComponentsClientHttpRequestFactory.setConnectTimeout(connectTimeout);
return httpComponentsClientHttpRequestFactory;
}
}
App Code:
#SpringBootApplication
public class App implements CommandLineRunner {
private static final Logger logger = LoggerFactory.getLogger(Application.class);
#Autowired
private GameSessionClient gameSessionClient;
public static void main(String[] args){
SpringApplication.run(Application.class, args);
}
#Override
public void run(String... args) throws Exception {
int playerNr = gameSessionClient.registerPlayer("test", true);
logger.info("Response: {}", playerNr);
}
}
The return restOperations.getForObject(url, int.class); results in a java.lang.NullPointerException
url: http://localhost:8080/registerPlayer/test/1 or http://localhost:8080/registerPlayer?name=test&mode=1 both result in 1 when using my browser
Any help would be much appreciated, as I'm getting pretty confused from this.
Update you code to below..will remove the NullPointer Exception your getting:
#Bean
public RestOperations restOperations(final ClientHttpRequestFactory clientHttpRequestFactory){
return new RestTemplate(clientHttpRequestFactory);
}
and do this , instead of "Application.class":
public static void main(String[] args){
SpringApplication.run(App.class, args);
}
Say I have a play Global file...
public class Global extends GlobalSettings {
private ApplicationContext ctx;
#Override
public void onStart(Application app) {
ctx = new ClassPathXmlApplicationContext("context/components.xml");
}
#Override
public <A> A getControllerInstance(Class<A> clazz) {
return ctx.getBean(clazz);
}
}
And I have a test like this...
#Test
public void itShouldFailOnMissingFields() throws Exception {
running(fakeApplication(), () -> {
Map<String, String> body = new HashMap<String, String>();
body.put("email", "jason#goodwin.com");
body.put("password", "ro");
FakeRequest request = new FakeRequest(POST, "/v1/profile")
.withFormUrlEncodedBody(body);
Result result = route(request);
assertEquals(status(result), 400);
}
);
}
How can I get test dependencies injected into a controller? Is there some other way I should be testing this?
There was an answer on google groups
https://groups.google.com/forum/#!topic/play-framework/MpjMQhEC2C4
I have a little problem. I think this is typical question. However, I can't find good example. My application is using Jersey. And I want to test controller by client as test. Controller has private field - StudentService. When I debug test I see, that field is null. This leads to error. And I need to inject this field. I tried this:
My Controller
#Path("/student")
#Component
public class StudentResourse {
#Autowired
private StrudentService service; // this field Spring does not set
#Path("/getStudent/{id}")
#GET
#Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public Student getStudent(#PathParam("id") long id) {
return service.get(id);
}
}
My JUnit test class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = "classpath:config.xml")
#TestExecutionListeners({ DbUnitTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class })
public class StudentResourseTest extends JerseyTest {
private static final String PACKAGE_NAME = "com.example.servlet";
private static final String FILE_DATASET = "/data.xml";
#Autowired
private StudentService service; // this field is setted by Spring, but I do not need this field for test
public StudentResourseTest() {
super(new WebAppDescriptor.Builder(PACKAGE_NAME).build());
}
#Override
protected TestContainerFactory getTestContainerFactory() {
return new HTTPContainerFactory();
}
#Override
protected AppDescriptor configure() {
return new WebAppDescriptor.Builder("restful.server.resource")
.contextParam("contextConfigLocation",
"classpath:/config.xml").contextPath("/")
.servletClass(SpringServlet.class)
.contextListenerClass(ContextLoaderListener.class)
.requestListenerClass(RequestContextListener.class).build();
}
#Test
#DatabaseSetup(FILE_DATASET)
public void test() throws UnsupportedEncodingException {
ClientResponse response = resource().path("student").path("getStudent")
.path("100500").accept(MediaType.APPLICATION_XML)
.get(ClientResponse.class);
Student student = (Student) response.getEntity(Student.class);
} }
I guees, that problem is in test class. Because, when I run my application not in test, I can directly request students and everything working fine. But when I test classes, internal field of Controller does not setted. How to fix this bug? Thanks for your answers.
This is in my config.xml
<context:component-scan base-package="com.example" />
<bean id="StudentResourse" class="com.example.servlet.StudentResourse">
<property name="service" ref="studentService" />
</bean>
<bean id="service" class="com.example.service.StudentServiceImpl" />
One issue may be that you're trying to configure your test application in constructor and in configure() method. Use one or another but not both because in this case your configure() method is not invoked and hence you may not be using SpringServlet and everything that is defined in this method.
Reference: https://github.com/jiunjiunma/spring-jersey-test and http://geek.riffpie.com/unit-testing-restful-jersey-services-glued-together-with-spring/
Idea is to get a hold of the application context inside jersey by using ApplicationContextAware interface. There after we can grab the exact bean already created by spring, in your case, StudentService. Below example shows a mocked version of the dependency, SampleService, used to test the resource layer apis.
Resource class delegating the processing to a service layer
#Component
#Path("/sample")
public class SampleResource {
#Autowired
private SampleService sampleService;
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path ("/{id}")
public Sample getSample(#PathParam("id") int id) {
Sample sample = sampleService.getSample(id);
if (sample == null) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
return sample;
}
}
Service layer encapsulating business logic
#Service
public class SampleService {
private static final Map<Integer, Sample> samples = new HashMap<>();
static {
samples.put(1, new Sample(1, "sample1"));
samples.put(2, new Sample(2, "sample2"));
}
public Sample getSample(int id) {
return samples.get(id);
}
}
Unit test for the above resource
public class SampleResourceTest extends SpringContextAwareJerseyTest {
private SampleService mockSampleService;
// create mock object for our test
#Bean
static public SampleService sampleService() {
return Mockito.mock(SampleService.class);
}
/**
* Create our own resource here so only the test resource is loaded. If
* we use #ComponentScan, the whole package will be scanned and more
* resources may be loaded (which is usually NOT what we want in a test).
*/
#Bean
static public SampleResource sampleResource() {
return new SampleResource();
}
// get the mock objects from the internal servlet context, because
// the app context may get recreated for each test so we have to set
// it before each run
#Before
public void setupMocks() {
mockSampleService = getContext().getBean(SampleService.class);
}
#Test
public void testMock() {
Assert.assertNotNull(mockSampleService);
}
#Test
public void testGetSample() {
// see how the mock object hijack the sample service, now id 3 is valid
Sample sample3 = new Sample(3, "sample3");
Mockito.when(mockSampleService.getSample(3)).thenReturn(sample3);
expect().statusCode(200).get(SERVLET_PATH + "/sample/3");
String jsonStr = get(SERVLET_PATH + "/sample/3").asString();
Assert.assertNotNull(jsonStr);
}
}
SpringContextAwareJerseyTest
#Configuration
public class SpringContextAwareJerseyTest extends JerseyTest {
protected static String SERVLET_PATH = "/api";
final private static ThreadLocal<ApplicationContext> context =
new ThreadLocal<>();
protected String getResourceLocation() {
return "example.rest";
}
protected String getContextConfigLocation() {
return getClass().getName();
}
static private String getContextHolderConfigLocation() {
return SpringContextAwareJerseyTest.class.getName();
}
protected WebAppDescriptor configure() {
String contextConfigLocation = getContextConfigLocation() + " " +
getContextHolderConfigLocation();
Map<String, String> initParams = new HashMap<>();
initParams.put("com.sun.jersey.config.property.packages",
getResourceLocation());
initParams.put("com.sun.jersey.api.json.POJOMappingFeature", "true");
return new WebAppDescriptor.Builder(initParams)
.servletClass(SpringServlet.class)
.contextParam(
"contextClass",
"org.springframework.web.context.support.AnnotationConfigWebApplicationContext")
.contextParam("contextConfigLocation", contextConfigLocation)
.servletPath(SERVLET_PATH) // if not specified, it set to root resource
.contextListenerClass(ContextLoaderListener.class)
.requestListenerClass(RequestContextListener.class)
.build();
}
protected final ApplicationContext getContext() {
return context.get();
}
#Bean
public static ContextHolder contextHolder() {
return new ContextHolder();
}
private static class ContextHolder implements ApplicationContextAware {
#Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
context.set(applicationContext);
}
}
}
Using the above with jersey 1.8
I'm testing a camel round but when I have the route try to access a particular method in a bean it keeps saying that there is no such method found. Source:
public class CommunicatorTest
{
FakeMessageConverter converter;
CamelContext context;
ProducerTemplate template;
String producerEndpoint = "seda:messagesFound";
long test = 123456789;
static final Logger logger = Logger.getLogger(CommunicatorTest.class);
public CommunicatorTest()
{
}
#Before
public void setUp() throws Exception
{
converter = new FakeMessageConverter();
SimpleRegistry registry = new SimpleRegistry();
registry.put("converter", converter);
context = new DefaultCamelContext(registry);
template = context.createProducerTemplate();
context.addRoutes(new CommunicatorRoute());
logger.info("Done creating context");
context.start();
}
#After
public void tearDown() throws Exception
{
context.stop();
}
#Test
public void testExistanceOfBean()
{
Object lookup = context.getRegistry().lookup("converter");
assertTrue("Object not a MessageConverter", lookup instanceof FakeMessageConverter);
}
#Test
public void testRoute()
{
Message msg = new Message();
msg.setHeader(new MessageHeader());
msg.getHeader().setSourceId(test);
logger.info("Sending data");
template.sendBody(producerEndpoint, msg);
assertEquals("value not the same", test, converter.getSid());
logger.info("Done Sending");
}
private static class FakeMessageConverter
{
private long sid;
private boolean edited = false;
public FakeMessageConverter()
{
}
public void processMessage(Message msg)
{
sid = msg.getHeader().getSourceId();
edited = true;
logger.info("The sid"+sid);
}
/**
* #return the sid
*/
public long getSid()
{
return sid;
}
/**
* #param sid the sid to set
*/
public void setSid(long sid)
{
this.sid = sid;
}
}
}
The route is as follows:
public class CommunicatorRoute extends RouteBuilder
{
#Override
public void configure() throws Exception
{
from("seda:messagesFound").bean("converter", "processMessage");
}
}
The exception is as follows:
ERROR [org.apache.camel.component.seda.SedaConsumer] - Error processing exchange. Exchange[Message: net.package.Message#f593af]. Caused by: [org.apache.camel.component.bean.MethodNotFoundException - Method with name: processMessage not found on bean: converter. Exchange[Message: net.package.message.Message#f593af]]
org.apache.camel.component.bean.MethodNotFoundException: Method with name: processMessage not found on bean: converter. Exchange[Message: com.saic.jswe.common.cdif.message.Message#f593af]
I'm still fairly new to camel so if I'm making some really simple mistake please let me know.
You class is private static. It must be public static so Camel can access it.
So I figured this out and it was a simple error. The routebuilder used the line from("seda:messagesFound").bean("converter", "processMessage"); which didn't work. BUT by changing the route to from("seda:messagesFound").beanref("converter", "processMessage"); and (as Claus mentions changing the access to public on the inner class) it worked fine. Just changing the class to public instead of private only changes the error it would seem.
The crux of the issue is that the .bean() method doesn't look at the registry, so when I passed it converter I thought I was giving it the name of the bean to use, but the only .bean() variation that fits is public Type bean(Object bean, String method). So of course camel couldn't find the method: It was trying to find the method "processMessage" not in my converter but in the very string I passed it.