fellow programmers who lurks here at Stack Overflow.
Today's question: How can I get the absolute baseUrl in Spring MVC Framework, from startup?
I'm working with Spring MVC Framework for an application, and I'm in this situation: Let's say that I need to make objects of class Foo, inserted into a list. Every object contains an unique self-link (I'm using org.springframework.hateoas.Link for the real application, but that's beside the point).
Example code:
class Foo{
private int ID;
private String absoluteUrl;
public Foo(int ID, String baseUrl){
this.ID = ID;
this.absoluteUrl = baseUrl + ID;
}
public void setAbsoluteUrl(String baseUrl){
this.absoluteUrl = baseUrl + this.ID;
}
}
If I run it through a factory, it could look something like this:
public List<Foo> GenerateFooList(String baseUrlFetchedBySpring){
List<Foo> list = new ArrayList();
for (int i = 0; i<100; i++){
list.add(new Foo(i, baseUrlFetchedBySpring);
return list;
}
Resulting baseadresses I would expect during the test phase would be "http://localhost:8000" (or hypothetically, at production, "http://www.fooExample.com").
My issue: I need to get the baseUrl from Spring MVC Framework, at startup.
The Spring MVC application I'm working with is configured by annotations only. I have found out that one can get an absolute url by using HttpServletRequest.getRequestURL().toString(), but to my understanding the application receives these after startup, while I need the baseUrl from the beginning. After all, the Spring API describes HttpServletRequest as: "Defines an object to provide client request information to a servlet", in other words a request sent from a client after startup.
I could of course add a static baseUrl by writing a private final String in the code:
private final String BASE_URL = "http://www.fooExample.com/"
But in case of changes on the application's base-url over time, it would be better if the base url could be set automaticly by Spring MVC. Let's say that I have a Cache-class, that uses dependency injection:
#Repository
class FooCache{
List<Foo> list;
SpringObject springObject; // = ???????????
#Autowired
public FooCache(SpringObject springObject){
this.springObject = springObject; // = ???????????
initCache();
}
public void initCache(){
for (int i = 0; i<100; i++){
list.add(new Foo(i, springObject.getAbsoluteBaseUrl()); // method = ???????????
}
}
This is more of what I am looking for: The cache is only set once, at the beginning, using an object from Spring that contains the information I need. Most likely, it's a config-class that is part of the framework, but after searching for a while on the Internet, what I mostly find is HttpServletRequest-related solutions.
What Spring class/object and method am I truly looking for? Or what other suggestions do you have to fetch the base_url from the beginning?
I need the absolute base url for this one, not something relative.
There is no single "base URL" for your application. Think about it - you can access your production server:
via different domain names
via IP
SSH to it and access via "localhost"
via HTTP and HTTPS
If you don't want to or cannot reflect the "base URL" of the request, my suggestion would be to have one "canonical base URL" configured per environment eg. in the properties. But that's up to you if it makes sense in your case.
If you're using java config and Servlet 3.0+ style (without web.xml), you could have a WebApplicationInitializer implementation such as:
public class MyWebApplicationInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext container) {
ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet());
registration.setLoadOnStartup(1);
registration.addMapping("/example/*");
}
}
with this arrangement, you could use servletContext.getContextPath() to get base url.
You have no way of knowing that without a request. You could use InetAddress and resolve host information, but I am guessing that is not what you are looking for.
Let's say you have two domains www.foo.com and www.bar.com pointing to the host where your application is running. On startup you have no information about any of that, you would have to do a reverse DNS search.
When you get a request, you have the information where it is coming from.
You should fill the links on request so you can get rid of caching request scope information.
Related
I am totally new to the whole Google Cloud Endpoint/Google App Engine world. If you have gone through the Hello World example that Google provides(and you probably have), you might remember that there are 2 classes that are auto-generated for you: MyBean and MyEndpoint.
These are something like this:
/**
* The object model for the data we are sending through endpoints
*/
public class MyBean {
private String myData;
public String getData() {
return myData;
}
public void setData(String data) {
myData = data;
}
}
And,
/**
* An endpoint class we are exposing
*/
#Api(
name = "myApi",
version = "v1",
namespace = #ApiNamespace(
ownerDomain = "backend.myapplication.DJ.example.com",
ownerName = "backend.myapplication.DJ.example.com",
packagePath = ""
)
)
public class MyEndpoint {
/**
* A simple endpoint method that takes a name and says Hi back
*/
#ApiMethod(name = "sayHi")
public MyBean sayHi(#Named("name") String name) {
MyBean response = new MyBean();
response.setData("Hi, " + name);
return response;
}
}
Now, I examined the code in index.html(which gets opened on deploying the backend). I found the following call in the javascript:
gapi.client.myApi.sayHi({'name': name}).execute(
Now, I can see that myApi is the name through annotation and sayHi() is the corresponding method, What I don't understand is the concept of exposing an API and annotation is aiding in that. There isn't any information about exposing APIs.
Could anyone help me understand this?
I think your question can be divided in 3 parts:
1/ What is exposing an API?
Basically, you are offering an access to your business logic through an Interface (the API), with full control on what you want to show or not. This Stack Exchange answer is a great explanation: https://softwareengineering.stackexchange.com/questions/203844/what-does-it-mean-to-expose-something
2/ What are these annotations in the auto-generated endpoints class?
If I can summarize it like that, Google endpoint is a "framework" which generates "default" APIs based on your java beans. By default you get CRUD operations in the API, but you can modify and/or enrich the endpoint to offer more complex business logic. The Endpoints classes that are generated include specific annotations that are used by the framework (in particular while generating a corresponding servlet) and define the URI you will call to interact with the methods of the APIs. See https://cloud.google.com/appengine/docs/java/endpoints/
3/ What is this gapi.client call?
gapi stands for Google API Client Library. It is the library that is offered by Google to interact with Endpoints from a web browser. See https://developers.google.com/api-client-library/javascript/
You could use other methods to call the Endpoint APIs, like jquery Ajax methods (http://api.jquery.com/jquery.ajax/), since Endpoints follow the REST architectural style. Note that you can call your endpoints from other "clients" than a web browser, e.g. an Android or iOS App. In these cases, you would not use the Google API Client Library but some other libraries.
I hope this clarifies a bit. Do not hesitate to ask for more details.
On every request made to the Restlet resources, I see the following logs in Google App Engine Logs
21:38:50.059 javax.servlet.ServletContext log: ExampleAPIs: [Restlet] ServerServlet: component class is null
21:38:51.568 javax.servlet.ServletContext log: ExampleAPIs: [Restlet] Attaching application: com.example.api.ExampleAPIConfig#68ec99 to URI: /example/v1
Why does it say Component is null?
I agree that I did not define Components rather used ServerResources and mapped them to the router in the Application class. But thats how it is supposed to be done as per the Restlet GAE Edition documentation.
Application class for wiring routes
public Example extends Application {
#Override
public Restlet createInboundRoot() {
router = new Router(getContext());
CorsService corsService = new CorsService();
corsService.setAllowedOrigins( new HashSet<String>(Arrays.asList("http://example.com")));
corsService.setAllowedCredentials(true);
getServices().add(corsService);
router.attach("/xyz", XYZ.class);
}
}
Server Resource which handles and returns a JSON Representation
public class XYZ extends ServerResource {
private static final Logger logger = Logger.getLogger("API:Xyz");
#Get(":json")
public Representation handleGetRequest() {
..
return new JsonRepresentation("{\"code\": 4008, \"description\": \"Something blah something\"}");
}
}
Is there something I am doing wrong ?
Did you configure your servlet configuration file as explained in document (below link).
I think servlet is not bound to a class.
https://restlet.com/technical-resources/restlet-framework/guide/2.3/editions/gae
Update
Ok so if you deeper in documentation :
https://restlet.com/technical-resources/restlet-framework/javadocs/2.0/jee/ext/org/restlet/ext/servlet/ServerServlet.html
https://restlet.com/technical-resources/restlet-framework/javadocs/2.0/jee/api/org/restlet/Component.html
You can see that component is optional but can be usefull, but maybe in GAE implementation it doesn't have one by default.
I've been going through this tutorial and they added a web service class instance to a hashSet, like this:
public class MessageApplication extends Application {
private Set<Object> singletons = new HashSet<Object>();
public MessageApplication() {
singletons.add(new MessageRestService());
}
#Override
public Set<Object> getSingletons() {
return singletons;
}
}
I do not understand what the purpose of it is... I thought you could just access the web service with a URL
You made a class, this class is able to handle web requests. But this class has to be hosted somewhere. This means, this class has to be activated by a URL route. In this case you're using JBOSS.
In the first option of the tutorial, MKyong shows you how to configure RESTEasy Bootstrap (a bootstrap to load references) to map the URL with your class. This is done in web.xml and configures some kind of scanner that will map a certain URL with your class.
The second option is not using RESTEasy Bootstrap and you have to add your class to a collection of long living objects in your application manually. This is done defining the Application (MessageAplication) and defining it in the web.xml.
Yes, you can access the webservice via a URL, but the server needs to know what to do with calls to a certain URL.
Yours is one way (the bootstrap version) of telling the application server where to look for JAX-RS resources: http://www.mastertheboss.com/resteasy/resteasy-tutorial (Step #4)
There is a (newer) alternative, depending on which server and RESTeasy-version you use, which relies on autoscanning for certain annotations. For RESTeasy on JBoss, it's described at the bottom of the tutorial-page I linked.
We are currently using a sync. web call to send emails. This was done as a quick fix to prove out some basic functionality, but now we need to send the e-mails async. I have the everything pretty much reworked to queue up jobs and then send the emails, but I've run in to one issue. We use FTL for our email templates and before we were passing the servlet context to FTL to get the template folder. Since we are now doing this in a queued job that get's processed by a Spring #Scheduled job, we no longer have access to the web servlet. I've been researching and playing around for awhile now, but I haven't seem to come up with a way that will actually work.
I have a feeling there is some super simple way to get
The code that did the work before looked similar to this:
#PUT
#Produces(MediaType.APPLICATION_JSON)
#Path("someStuffHere")
#Transactional
public function someWebServiceCall(#Context javax.servlet.http.HttpServletRequest req)
{
SomeStuff foo = gotSomeStuff();
sendEmail(req.getServlet(), foo);
}
public sendEmail(Servlet context, SomeStuff foo) //<-- lives in another class somewhere, just showing how FTL uses the servlet
{
Configuration cfg = new Configuration();
cfg.setServletContextForTemplateLoading(context,"communicationTemplates/email");
}
The new code now looks something like this:
public class someClass
{
#Autowired
private SomeRepo someRepo;
#Scheduled(cron = "* */2 * * * ?")
public void sendAnyOutstandingStuffEmails()
{
SomeStuff foo = someRepo.getStuff();
sendEmail(/*how to get context, or a resource so FTL can get the template folder*/, foo)
}
Even though this post is quite old and the author has given another solution a try, it is not necessary to have a servlet context instance in order to load templates. The freemarker documentation states:
Built-in template loaders
You can set up three template loading methods in the Configuration
using the following convenience methods. (Each method will create a
template loader object internally and set up the Configuration
instance to use that.)
void setDirectoryForTemplateLoading(File dir);
or
void setClassForTemplateLoading(Class cl, String prefix);
or
void setServletContextForTemplateLoading(Object servletContext, String path);
http://freemarker.org/docs/pgui_config_templateloading.html
So in this case it should have been possible to configure freemarker to use the ClassLoader (option 2) by naming a class that is on the same level as the templates or to use this class as root node for navigating to the template using relative paths.
Is it possible to get the application URL in spring scheduled job (#Scheduled annotated)?
I want to create a job which sends an email with URL to specific page on the application, let's say on http://localhost:8080/appName/some/specific/url. The problem is that the part http://localhost:8080/ will be different in each environment (local,dev,production etc).
Is there any simple way to get a base URL in server-side method which is executed by spring scheduler?
I do that with a properties file. This tutorial tells you how you can do it.
The only complex part is you need a way to change the value the properties file is referencing for each of your different environments.
There is no direct way to get the base url within your scheduler. You may want to look at work arounds for this, like
Use a properties file to store url for each environment
Have a Configurartion bean which implements ServletContextAware. This bean would be automatically notified when a web context is initialised.
public class AppConfig implements ServletContextAware{
private String baseUrl;
public String getBaseUrl(){
return baseUrl;
}
public void setServletContext(ServletContext servletContext){
this.baseUrl=servletContext.getRealPath("/");
}
}