Search This Blog

Showing posts with label Spring Boot. Show all posts
Showing posts with label Spring Boot. Show all posts

Thursday, 3 September 2020

java-cfenv : A library for accessing Cloud Foundry Services on the new Tanzu Application Service for Kubernetes

The Spring Cloud Connectors library has been with us since the launch event of Cloud Foundry itself back in 2011. This library would create the required Spring Beans from bound VCAP_SERVICE ENV variable from a pushed Cloud Foundry Application such as connecting to databases for example. The java buildpack then replaces these bean definitions you had in your application with those created by the connector library through a feature called ‘auto-reconfiguration’

Auto-reconfiguration is great for getting started. However, it is not so great when you want more control, for example changing the size of the connection pool associated with a DataSource.

With the up coming Tanzu Application Service for Kubernetes the original Cloud Foundry buildpacks are now replaced with the new Tanzu Buildpacks which are based on the Cloud Native Buildpacks CNCF Sandbox project. As a result of this auto-reconfiguration is no longer included in java cloud native buildpacks which means auto-configuration for the backing services is no longer available.

So is their another option for this? The answer is "Java CFEnv". This provide a simple API for retrieving credentials from the JSON strings contained inside the VCAP_SERVICES environment variable.

https://github.com/pivotal-cf/java-cfenv



So if you after exactly how it worked previously all you need to do is add this maven dependancy to your project as shown below.

  
        <dependency>
            <groupId>io.pivotal.cfenv</groupId>
            <artifactId>java-cfenv-boot</artifactId>
        </dependency>

Of course this new library is much more flexible then this and by using the class CfEnv as the entry point to the API for accessing Cloud Foundry environment variables your free to use the Spring Expression Language to invoke methods on the bean of type CfEnv to set properties for example plus more.

For more information read the full blog post as per below

https://spring.io/blog/2019/02/15/introducing-java-cfenv-a-new-library-for-accessing-cloud-foundry-services

Finally this Spring Boot application is an example of using this new library with an application deployed to the new Tanzu Application Service for Kubernetes.

https://github.com/papicella/spring-book-service


More Information

1. Introducing java-cfenv: A new library for accessing Cloud Foundry Services

https://spring.io/blog/2019/02/15/introducing-java-cfenv-a-new-library-for-accessing-cloud-foundry-services

2. Java CFEnv GitHub Repo

https://github.com/pivotal-cf/java-cfenv#pushing-your-application-to-cloud-foundry

Friday, 10 July 2020

Multi-Factor Authentication (MFA) using OKTA with Spring Boot and Tanzu Application Service

Recently I was asked to build a quick demo showing how to use MFA with OKTA and Spring Boot application running on Tanzu Application Service. Here is the demo application plus how to setup and run this yourself.

Steps

1. Clone the existing repo as shown below

$ git clone https://github.com/papicella/mfa-boot-fsi
Cloning into 'mfa-boot-fsi'...
remote: Enumerating objects: 47, done.
remote: Counting objects: 100% (47/47), done.
remote: Compressing objects: 100% (31/31), done.
remote: Total 47 (delta 2), reused 47 (delta 2), pack-reused 0
Unpacking objects: 100% (47/47), done.



2. Create a free account of https://developer.okta.com/

Once created login to the dev account. Your account URL will look like something as follows

https://dev-{ID}-admin.okta.com



3. You will need your default authorization server settings. From the top menu in the developer.okta.com dashboard, go to API -> Authorization Servers and click on the default server


You will need this data shortly. Image above is an example those details won't work for your own setup.

4. From the top menu, go to Applications and click the Add Application button. Click on the Web button and click Next. Name your app whatever you like. I named mine "pas-okta-springapp". Otherwise the default settings are fine. Click Done.

From this screen shot you can see that the default's refer to localhost which for DEV purposes is fine.


You will need the Client ID and Client secret from the final screen so make a note of these

5. Edit the "./mfa-boot-fsi/src/main/resources/application-DEV.yml" to include the details as per #3 and #4 above.

You will need to edit

  • issuer
  • client-id
  • client-secret


application-DEV.yaml

spring:
  security:
    oauth2:
      client:
        provider:
          okta:
            user-name-attribute: email

okta:
  oauth2:
    issuer: https://dev-213269.okta.com/oauth2/default
    redirect-uri: /authorization-code/callback
    scopes:
      - profile
      - email
      - openid
    client-id: ....
    client-secret: ....

6. In order to pick up this application-DEV.yaml we have to set the spring profile correctly. That can be done using a JVM property as follows.

-Dspring.profiles.active=DEV

In my example I use IntelliJ IDEA so I set it on the run configurations dialog as follows



7. Finally let's setup MFA which we do as follows by switching to classic UI as shown below



8. Click on Security -> Multifactor and setup another Multifactor policy. In the screen shot below I select "Email Policy" and make sure it is "Required" along with the default policy



9. Now run the application making sure you set the spring active profile to DEV.

...
2020-07-10 13:34:57.528  INFO 55990 --- [  restartedMain] pas.apa.apj.mfa.demo.DemoApplication     : The following profiles are active: DEV
...

10. Navigate to http://localhost:8080/



11. Click on the "Login" button

Verify you are taken to the default OKTA login page


12. Once logged in the second factor should then ask for a verification code to be sent to your email. Press the "Send me the code" button




13. Once you enter the code sent to your email you will be granted access to the application endpoints







14. Finally to deploy the application to Tanzu Application Service perform these steps below

- Create a manifest.yaml as follows

---
applications:
- name: pas-okta-boot-app 
  memory: 1024M
  buildpack: https://github.com/cloudfoundry/java-buildpack.git#v4.16
  instances: 2
  path: ./target/demo-0.0.1-SNAPSHOT.jar
  env:
    JBP_CONFIG_OPEN_JDK_JRE: '{ jre: { version: 11.+}}'

- Package the application as follows

$ ./mvnw -DskipTests package

- In the DEV OTKA console create a second application which will be for the deployed application on Tanzu Application Service which refers to it's FQDN rather then localhost as shown below



- Edit "application.yml" to ensure you set the following correctly for the new "Application" we created above.

You will need to edit

  • issuer
  • client-id
  • client-secret
- Push the application using "cf push -f manifest.yaml"

$ cf apps
Getting apps in org papicella-org / space apple as papicella@pivotal.io...
OK

name                requested state   instances   memory   disk   urls
pas-okta-boot-app   started           1/1         1G       1G     pas-okta-boot-app.cfapps.io


That's It!!!!

Thursday, 7 February 2019

Spring Cloud GCP and authentication from your Spring Boot Application

When using Spring Cloud GCP you will need to authenticate at some point in order to use the GCP services. In this example below using a GCP Cloud SQL instance you really only need to do 3 things to access it externally from your Spring Boot application as follows.

1. Enable the Google Cloud SQL API which is detailed here

  https://cloud.google.com/sql/docs/mysql/admin-api/

2. Ensure that your GCP SDK can login to your Google Cloud SQL. This command will take you to a web page asking which google account you want to use

  $ gcloud auth application-default login

3. Finally some application properties in your Spring Boot application detailing the Google Cloud SQL instance name and database name as shown below.

spring.cloud.gcp.sql.instance-connection-name=fe-papicella:australia-southeast1:apples-db
spring.cloud.gcp.sql.database-name=employees

Now when you do that and your application starts up you will see a log message as follows below clearly warning you this this method of authentication can have implications at some point.

2019-02-07 09:10:26.700  WARN 2477 --- [           main] c.g.a.oauth2.DefaultCredentialsProvider  : Your application has authenticated using end user credentials from Google Cloud SDK. We recommend that most server applications use service accounts instead. If your application continues to use end user credentials from Cloud SDK, you might receive a "quota exceeded" or "API not enabled" error. For more information about service accounts, see https://cloud.google.com/docs/authentication/.

Clearly that's something we have to resolve. To do that we simply can add another Spring Boot application property pointing to a service account JSON file for us to authenticate against to remove the warning.

spring.cloud.gcp.credentials.location=file:/Users/papicella/piv-projects/GCP/fe-papicella-8077fe1126b2.json

Note: You can also use an ENV variable as follows

export GOOGLE_APPLICATION_CREDENTIALS="[PATH]"

You can get a JSON key generated from the GCP console "IAM and Admin -> Service Accounts" page


For more information on authentication visit this link https://cloud.google.com/docs/authentication/getting-started



Friday, 15 September 2017

Using Cloud Foundry CUPS to inject Spring Security credentials into a Spring Boot Application

The following demo shows how to inject the Spring Security username/password credentials from a User Provided service on PCF, hence using the VCAP_SERVICES env variable to inject the values required to protect the application using HTTP Basic Authentication while running in PCF. Spring Boot automatically converts this data into a flat set of properties so you can easily get to the data as shown below.

The demo application can be found as follows

https://github.com/papicella/springsecurity-cf-cups

The application.yml would access the VCAP_SERVICES CF env variable using the the Spring Boot flat set of properties as shown below.

eg:

VCAP_SERVICES

System-Provided:
{
 "VCAP_SERVICES": {
  "user-provided": [
   {
    "credentials": {
     "password": "myadminpassword",
     "username": "myadminuser"
    },
    "label": "user-provided",
    "name": "my-cfcups-service",
    "syslog_drain_url": "",
    "tags": [],
    "volume_mounts": []
   }
  ]
 }
}
...

application.yml

spring:
  application:
    name: security-cf-cups-demo
security:
  user:
    name: ${vcap.services.my-cfcups-service.credentials.username:admin}
    password: ${vcap.services.my-cfcups-service.credentials.password:password}

Wednesday, 14 June 2017

Swagger UI with Spring Boot 1.5.x

I recently created this demo / blog entry on using HTTPIE with Spring Boot Rest Repositories as shown below.

http://theblasfrompas.blogspot.com.au/2017/05/using-httpie-with-spring-boot-rest.html

I decided to take that same example and add Swagger UI to the RESTful endpoints. The full source code is here.

https://github.com/papicella/httpie-springboot

In short what you need is the following maven dependancies and that will add all you need. I found it works much cleaner if you use the same version of both these dependancies for some reason
  
<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger2</artifactId>
  <version>2.6.1</version>
</dependency>
<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger-ui</artifactId>
  <version>2.6.1</version>
</dependency>

Finally a Class file describing the config and enabling Swagger is required as follows
  
package pivotal.io.boot.httpie.demo;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import static springfox.documentation.builders.PathSelectors.regex;

@Configuration
@EnableSwagger2
public class SwaggerConfig
{
    @Bean
    public Docket swaggerSpringMvcPlugin() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.basePackage("pivotal.io.boot.httpie.demo"))
                .paths(regex("/api/employee/emps.*"))
                .build()
                .apiInfo(metaData());
    }

    private ApiInfo metaData() {
        ApiInfo apiInfo = new ApiInfo(
                "Spring Boot Employee REST API",
                "Spring Boot Employee REST API",
                "1.0",
                "Terms of service",
                new Contact("Pas Apicella", "https://www.blogger.com/profile/09389663166398991762", "papicella@pivotal.io"),
                "Apache License Version 2.0",
                "https://www.apache.org/licenses/LICENSE-2.0");
        return apiInfo;
    }
}

The GitHub repo also included a Pivotal Cloud Foundry manifest.yml file to make it easy to deploy to Pivotal Cloud Foundry. The example uses a static hostname BUT can easily be changed to use a random-route or alter the hostname itself.

applications:
- name: pas-swagger-demo
  memory: 1G
  instances: 1
  hostname: pas-swagger-demo
  path: ./target/httpie-springboot-0.0.1-SNAPSHOT.jar
  env:
    JAVA_OPTS: -Djava.security.egd=file:///dev/urando

Then it's the simple "cf push"

$ cf push

pasapicella@pas-macbook:~/piv-projects/httpie-springboot$ cf push
Using manifest file /Users/pasapicella/piv-projects/httpie-springboot/manifest.yml

Creating app pas-swagger-demo in org apples-pivotal-org / space development as papicella@pivotal.io...
OK

Creating route pas-swagger-demo.cfapps.io...
OK

..

Showing health and status for app pas-swagger-demo in org apples-pivotal-org / space development as papicella@pivotal.io...
OK

requested state: started
instances: 1/1
usage: 1G x 1 instances
urls: pas-swagger-demo.cfapps.io
last uploaded: Wed Jun 14 03:32:31 UTC 2017
stack: cflinuxfs2
buildpack: container-certificate-trust-store=2.0.0_RELEASE java-buildpack=v3.15-offline-https://github.com/cloudfoundry/java-buildpack.git#a3a9e61 java-main java-opts open-jdk-like-jre=1.8.0_121 open-jdk-like-memory-calculator=2.0.2_RELEASE spring-auto-reconfigur...

     state     since                    cpu      memory         disk           details
#0   running   2017-06-14 01:33:40 PM   291.5%   510.9M of 1G   154.9M of 1G


The application is running on Pivotal Web Services as follows:

http://pas-swagger-demo.cfapps.io/swagger-ui.html



Monday, 22 May 2017

Using HTTPIE with Spring Boot Rest Repositories

I recently got introduced to HTTPIE as a command line alternative to CURL for testing RESTful api endpoints created using @RestController annotated classes. For more information on httpie follow this link

Before we test this out lets create a very basic Spring Boot Application with classes/interfaces to verify HTTPIE. The following assumes you have a Spring Boot application already created and it has maven dependancies as follows to enable JPA, Rest Repositories, H2 and Web support

Note: We are using Spring Boot 1.5.3 here
  
<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>1.5.3.RELEASE</version>
  <relativePath/> <!-- lookup parent from repository -->
 </parent>

 <properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  <java.version>1.8</java.version>
 </properties>

 <dependencies>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-rest</artifactId>
  </dependency>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>
  <dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-entitymanager</artifactId>
  </dependency>
  <dependency>
   <groupId>com.h2database</groupId>
   <artifactId>h2</artifactId>
   <scope>runtime</scope>
  </dependency>
 </dependencies>

1. Create classes/interfaces as follows

Employee.java
  
package pivotal.io.boot.httpie.demo;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Employee
{
    @Id
    @GeneratedValue (strategy = GenerationType.AUTO)
    private Long id;

    private String firstName;
    private String lastName;
    private String job;

    public Employee()
    {
    }

    public Employee(String firstName, String lastName, String job) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.job = job;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                ", job='" + job + '\'' +
                '}';
    }
}

EmployeeRepository.java
  
package pivotal.io.boot.httpie.demo;

import org.springframework.data.jpa.repository.JpaRepository;

public interface EmployeeRepository extends JpaRepository <Employee, Long> {
}  

EmployeeRest.java
  
package pivotal.io.boot.httpie.demo;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping ("/api/employee")
public class EmployeeRest
{
    private static Log logger = LogFactory.getLog(EmployeeRest.class);

    @Autowired
    private EmployeeRepository employeeRepository;

    @GetMapping("/emps")
    public List<Employee> allEmployees()
    {
        return employeeRepository.findAll();
    }

    @GetMapping("/emp/{employeeId}")
    public Employee findEmployee (@PathVariable Long employeeId)
    {
        Employee emp = employeeRepository.findOne(employeeId);

        return emp;
    }

    @PostMapping("/emps")
    public Employee createEmployee(@RequestBody Employee employee)
    {
        return employeeRepository.save(employee);
    }

    @DeleteMapping("/emps/{employeeId}")
    public void deleteEmployee(@PathVariable Long employeeId)
    {
        Employee emp = employeeRepository.findOne(employeeId);
        employeeRepository.delete(emp);
        logger.info("Employee with id " + employeeId + " deleted...");
    }

}

2. Run the Spring Boot Application which will run on port localhost:8080


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.3.RELEASE)

2017-05-22 13:39:22.910  INFO 8875 --- [           main] p.i.b.h.d.HttpieSpringbootApplication    : Starting HttpieSpringbootApplication on pas-macbook with PID 8875 (/Users/pasapicella/pivotal/DemoProjects/spring-starter/pivotal/httpie-springboot/target/classes started by pasapicella in /Users/pasapicella/pivotal/DemoProjects/spring-starter/pivotal/httpie-springboot)

...

2017-05-22 13:39:25.948  INFO 8875 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2017-05-22 13:39:25.952  INFO 8875 --- [           main] p.i.b.h.d.HttpieSpringbootApplication    : Started HttpieSpringbootApplication in 3.282 seconds (JVM running for 3.676)

Now we can test HTTPIE and here are some endpoints

3. Here are some examples with output

** All Employees **

pasapicella@pas-macbook:~$ http http://localhost:8080/api/employee/emps
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Date: Mon, 22 May 2017 01:26:43 GMT
Transfer-Encoding: chunked

[
    {
        "firstName": "pas",
        "id": 1,
        "job": "CEO",
        "lastName": "Apicella"
    },
    {
        "firstName": "lucia",
        "id": 2,
        "job": "CIO",
        "lastName": "Apicella"
    },
    {
        "firstName": "lucas",
        "id": 3,
        "job": "MANAGER",
        "lastName": "Apicella"
    },
    {
        "firstName": "siena",
        "id": 4,
        "job": "CLERK",
        "lastName": "Apicella"
    }
]

** Find Employee by {employeeId} **

pasapicella@pas-macbook:~$ http http://localhost:8080/api/employee/emp/1
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Date: Mon, 22 May 2017 01:31:32 GMT
Transfer-Encoding: chunked

{
    "firstName": "pas",
    "id": 1,
    "job": "CEO",
    "lastName": "Apicella"
}

** POST new employee **

pasapicella@pas-macbook:~$ http POST http://localhost:8080/api/employee/emps firstName=john lastName=black job=CLERK
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Date: Mon, 22 May 2017 02:32:34 GMT
Transfer-Encoding: chunked

{
    "firstName": "john",
    "id": 5,
    "job": "CLERK",
    "lastName": "black"
}

** POST with updated employee object **

pasapicella@pas-macbook:~$ http POST http://localhost:8080/api/employee/emps id:=5 firstName=john lastName=black job=CLEANER
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Date: Mon, 22 May 2017 02:36:06 GMT
Transfer-Encoding: chunked

{
    "firstName": "john",
    "id": 5,
    "job": "CLEANER",
    "lastName": "black"
}

** Delete employee with {employeeId} 5 **

pasapicella@pas-macbook:~$ http DELETE http://localhost:8080/api/employee/emps/5
HTTP/1.1 200
Content-Length: 0
Date: Mon, 22 May 2017 02:36:56 GMT

Wednesday, 26 April 2017

Cross-origin resource sharing (CORS) from Spring Boot Rest Controllers

Was involved in a hackathon recently and after creating a few Spring boot API's for the UI team to consume and they run into errors around (Cross-origin resource sharing ). For security reasons, browsers prohibit AJAX calls to resources residing outside the current origin.

I have seen this before and Spring Boot has support to ensure you can control which resources can be accessed outside of the current origin. It's as simple as an annotation "@CrossOrigin", as shown below. In this example every request from this Rest Controller supports resource calls residing outside the current origin.
  
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@CrossOrigin
@RestController
@RequestMapping(value = "/beacon")
public class BeaconRest
{
    private static Log logger = LogFactory.getLog(BeaconRest.class);

    @Autowired
    private BeaconRepository beaconRepository;

    @RequestMapping(value = "/all",
            method = RequestMethod.GET,
            produces = MediaType.APPLICATION_JSON_VALUE)
    public List<Beacon> allBeacons()
    {
        logger.info("Invoking /beacon/all RESTful method");
        return beaconRepository.findAll();
    }

Of course it's much more flexible then that adding the ability to add options, and you can read more about it here.

https://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/cors.html

Tuesday, 31 January 2017

Retrieving ATM location's using the NAB API

NAB have released an API to determine ATM Locations amongst other things based on a GEO location. It's documented here.

https://developer.nab.com.au/docs#locations-api-get-locations-by-geo-coordinates

Here we use this API but I wanted to highlight a few things you would need to know to consume the API

1. You will need to provide the following and this has to be calculated based on a LAT/LONG. The screen shot below shows what a GEO location with a radius of 0.5 KM's would look like. You will see it's starting point is your current location in this case in Melbourne CBD.



2. The NAP API would required the following to be set which can be obtained using a calculation as per the screen shot above

swLat South-West latitude coordinates expressed as decimal
neLat North-East latitude coordinates expressed as decimal
neLng North-East longitude coordinates expressed as decimal
swLng South-West longitude coordinates expressed as decimal

3. The attribute LocationType allows you to filter what type of location your after I set this to "ATM" to only find ATM locations.

4. I also set the attribute Fields to extended as this gives me detailed information

5. Once you have the data here is an example of getting detailed information of ATM locations using the GEO location co-ordinates. In this example CURL is good enough to illustrate that

pasapicella@pas-macbook:~/pivotal$ curl -H "x-nab-key: NABAPI-KEY" "https://api.developer.nab.com.au/v2/locations?locationType=atm&searchCriteria=geo&swLat=-37.81851471355399&swLng=144.95235719310358&neLat=-37.812155549503025&neLng=144.96040686020137&fields=extended&v=1" | jq -r
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 10371  100 10371    0     0   2016      0  0:00:05  0:00:05 --:--:--  2871
{
  "locationSearchResponse": {
    "totalRecords": 16,
    "viewport": {
      "swLat": -37.81586424048582,
      "swLng": 144.9589117502319,
      "neLat": -37.81109231077813,
      "neLng": 144.96758064976802
    },
    "locations": [
      {
        "apiStructType": "atm",
        "atm": {
          "location": "Offsite",
          "isDeposit": false,
          "isDisabilityApproved": true,
          "isAudio": false,
          "source": "NAB ATM",
          "address3": "Melbourne Central",
          "address4": "Lower Ground floor",
          "id": 5058548,
          "key": "atm_3B46",
          "description": "Melbourne Central",
          "address1": "300 Elizabeth Street",
          "suburb": "Melbourne",
          "state": "VIC",
          "postcode": "3000",
          "latitude": -37.812031,
          "longitude": 144.9621768,
          "hours": "Mon-Thu 10.00am-06.00pm, Fri 10.00am-09.00pm, Sat 10.00am-06.00pm, Sun 10.00am-05.00pm"
        }
      },
      {
        "apiStructType": "atm",
        "atm": {
          "location": "Offsite",
          "isDeposit": false,
          "isDisabilityApproved": true,
          "isAudio": false,
          "source": "NAB ATM",
          "address3": "Melbourne Central",
          "address4": "Ground Floor",
          "address5": "Near La Trobe St Entrance under escalator",
          "id": 5058552,
          "key": "atm_3B56",
          "description": "Melbourne Central",
          "address1": "300 Elizabeth Street",
          "suburb": "Melbourne",
          "state": "VIC",
          "postcode": "3000",
          "latitude": -37.812031,
          "longitude": 144.9621768,
          "hours": "Mon-Thu 10.00am-06.00pm, Fri 10.00am-09.00pm, Sat 10.00am-06.00pm, Sun 10.00am-05.00pm"
        }
      },
      {
        "apiStructType": "atm",
        "atm": {
          "location": "Offsite",
          "isDeposit": false,
          "isDisabilityApproved": true,
          "isAudio": false,
          "source": "NAB ATM",
          "address3": "Queen Victoria Market",
          "address5": "Outside the market facing the street",
          "id": 5058555,
          "key": "atm_3B61",
          "description": "Queen Victoria Market",
          "address1": "Queen Street",
          "address2": "Corner Therry Street",
          "suburb": "Melbourne",
          "state": "VIC",
          "postcode": "3000",
          "latitude": -37.8130009,
          "longitude": 144.9597905,
          "hours": "24/7"
        }
      },
      {
        "apiStructType": "atm",
        "atm": {
          "location": "Offsite",
          "isDeposit": false,
          "isDisabilityApproved": true,
          "isAudio": false,
          "source": "NAB ATM",
          "address3": "Target Centre",
          "id": 5058577,
          "key": "atm_3CC7",
          "description": "Target Centre",
          "address1": "236 Bourke Street Mall",
          "suburb": "Melbourne",
          "state": "VIC",
          "postcode": "3000",
          "latitude": -37.8132227,
          "longitude": 144.9665518,
          "hours": "Mon-Fri 09.00am-05.00pm, Sat-Sun 10.00am-05.00pm"
        }
      },
      {
        "apiStructType": "atm",
        "atm": {
          "location": "Branch",
          "isDeposit": true,
          "isDisabilityApproved": true,
          "isAudio": false,
          "source": "NAB ATM",
          "address3": "Queen Victoria Centre",
          "id": 5058614,
          "key": "atm_3F07",
          "description": "Queen Victoria",
          "address1": "228-234 Lonsdale Street",
          "suburb": "Melbourne",
          "state": "VIC",
          "postcode": "3000",
          "latitude": -37.8122729,
          "longitude": 144.9622383,
          "hours": "24/7"
        }
      },
      {
        "apiStructType": "atm",
        "atm": {
          "location": "Offsite",
          "isDeposit": false,
          "isDisabilityApproved": true,
          "isAudio": false,
          "source": "NAB ATM",
          "address3": "University of Melbourne",
          "address5": "Kenneth Myer Building",
          "id": 5058653,
          "key": "atm_3G28",
          "description": "KMB Foyer",
          "address1": "30 Royal Parade",
          "suburb": "Parkville",
          "state": "VIC",
          "postcode": "3052",
          "latitude": -37.8149256,
          "longitude": 144.9643156,
          "hours": "Mon-Fri 07.00am-07.00pm"
        }
      },
      {
        "apiStructType": "atm",
        "atm": {
          "location": "Branch",
          "isDeposit": true,
          "isDisabilityApproved": true,
          "isAudio": false,
          "source": "SmartATM",
          "address3": "Midtown Plaza",
          "address4": "Shop 8",
          "id": 5058783,
          "key": "atm_3S02",
          "description": "Midtown Plaza",
          "address1": "186 Swanston Street",
          "suburb": "Melbourne",
          "state": "VIC",
          "postcode": "3000",
          "latitude": -37.8131315,
          "longitude": 144.9654723,
          "hours": "Mon-Fri 09.30am-05.00pm, Sat 10.00am-02.00pm"
        }
      },
      {
        "apiStructType": "atm",
        "atm": {
          "location": "Branch",
          "isDeposit": true,
          "isDisabilityApproved": true,
          "isAudio": false,
          "source": "SmartATM",
          "address3": "Midtown Plaza",
          "address4": "Shop 8",
          "id": 5058784,
          "key": "atm_3S03",
          "description": "Midtown Plaza",
          "address1": "186 Swanston Street",
          "suburb": "Melbourne",
          "state": "VIC",
          "postcode": "3000",
          "latitude": -37.8131315,
          "longitude": 144.9654723,
          "hours": "Mon-Fri 09.30am-05.00pm, Sat 10.00am-02.00pm"
        }
      },
      {
        "apiStructType": "atm",
        "atm": {
          "location": "Branch",
          "isDeposit": true,
          "isDisabilityApproved": true,
          "isAudio": false,
          "source": "SmartATM",
          "address3": "Melbourne NAB House",
          "id": 5058814,
          "key": "atm_3S38",
          "description": "National Bank House",
          "address1": "500 Bourke Street",
          "suburb": "Melbourne",
          "state": "VIC",
          "postcode": "3000",
          "latitude": -37.8154128,
          "longitude": 144.9590017
        }
      },
      {
        "apiStructType": "atm",
        "atm": {
          "location": "Branch",
          "isDeposit": true,
          "isDisabilityApproved": true,
          "isAudio": false,
          "source": "SmartATM",
          "address3": "Melbourne NAB House",
          "id": 5058815,
          "key": "atm_3S39",
          "description": "National Bank House",
          "address1": "500 Bourke Street",
          "suburb": "Melbourne",
          "state": "VIC",
          "postcode": "3000",
          "latitude": -37.8154128,
          "longitude": 144.9590017,
          "hours": "24/7"
        }
      },
      {
        "apiStructType": "atm",
        "atm": {
          "location": "Branch",
          "isDeposit": true,
          "isDisabilityApproved": true,
          "isAudio": false,
          "source": "SmartATM",
          "address3": "Melbourne NAB House",
          "id": 5058837,
          "key": "atm_3S67",
          "description": "National Bank House",
          "address1": "500 Bourke Street",
          "suburb": "Melbourne",
          "state": "VIC",
          "postcode": "3000",
          "latitude": -37.8154128,
          "longitude": 144.9590017,
          "hours": "24/7"
        }
      },
      {
        "apiStructType": "atm",
        "atm": {
          "location": "Branch",
          "isDeposit": true,
          "isDisabilityApproved": true,
          "isAudio": false,
          "source": "SmartATM",
          "address3": "Midtown Plaza",
          "address4": "Shop 8",
          "id": 5058842,
          "key": "atm_3S72",
          "description": "Midtown Plaza",
          "address1": "186 Swanston Street",
          "suburb": "Melbourne",
          "state": "VIC",
          "postcode": "3000",
          "latitude": -37.8131315,
          "longitude": 144.9654723,
          "hours": "24/7"
        }
      },
      {
        "apiStructType": "atm",
        "atm": {
          "location": "Offsite",
          "isDeposit": false,
          "isDisabilityApproved": true,
          "isAudio": false,
          "source": "NAB ATM",
          "address3": "Midtown Plaza",
          "id": 5059024,
          "key": "atm_4G04",
          "description": "Midtown Plaza",
          "address1": "194 Swanston Street",
          "suburb": "Melbourne",
          "state": "VIC",
          "postcode": "3000",
          "latitude": -37.8130332,
          "longitude": 144.9654279,
          "hours": "Mon-Tue 09.30am-05.30pm, Wed-Fri 09.30am-09.00pm, Sat 09.30am-05.30pm, Sun 11.00am-05.00pm"
        }
      },
      {
        "apiStructType": "atm",
        "atm": {
          "location": "Branch",
          "isDeposit": true,
          "isDisabilityApproved": true,
          "isAudio": false,
          "source": "BOQ ATM",
          "id": 5059452,
          "key": "atm_9036021I",
          "description": "455 Bourke Street",
          "address1": "455 Bourke Street",
          "suburb": "Melbourne",
          "state": "VIC",
          "postcode": "3000",
          "latitude": -37.81518,
          "longitude": 144.960583
        }
      },
      {
        "apiStructType": "atm",
        "atm": {
          "location": "Branch",
          "isDeposit": false,
          "isDisabilityApproved": false,
          "isAudio": false,
          "source": "rediATM",
          "id": 5060659,
          "key": "atm_C11243",
          "description": "Bourke Street",
          "address1": "460 Bourke Street",
          "suburb": "Melbourne",
          "state": "VIC",
          "postcode": "3000",
          "latitude": -37.81494,
          "longitude": 144.96002,
          "hours": "24/7"
        }
      },
      {
        "apiStructType": "atm",
        "atm": {
          "location": "Offsite",
          "isDeposit": false,
          "isDisabilityApproved": false,
          "isAudio": false,
          "source": "rediATM",
          "address3": "Emporium Melbourne",
          "address5": "ATM 02",
          "id": 5060908,
          "key": "atm_C11662",
          "description": "Emporium Melbourne",
          "address1": "269-321 Lonsdale Street",
          "suburb": "Melbourne",
          "state": "VIC",
          "postcode": "3000",
          "latitude": -37.811932,
          "longitude": 144.963648,
          "hours": "Mon-Wed 10.00am-07.00pm, Thu-Fri 10.00am-09.00pm, Sat-Sun 10.00am-07.00pm"
        }
      }
    ]
  },
  "status": {
    "code": "API-200",
    "message": "Success"
  }
}

Monday, 9 January 2017

Spring Boot Application Consuming NAB FxRates API

National Australia Bank (NAB) recently released a set of BETA API's as per the link below.

https://developer.nab.com.au/

The following example is a Spring Boot Application that consumes the FxRates API. It's all documented on GitHub and you will need a NAB API key to run this demo. It can run stand alone as a FAT JAR through Spring Boot or deployed to Cloud Foundry, instructions appear for both.

https://github.com/papicella/NABApi-fx-demo

 

Wednesday, 14 December 2016

Spring Boot with Feign and Twitter Typeahead JS library

I previously blogged about a demo using Spring Boot and Feign making an external based REST call to a service I had created. The real purpose of that demo "http://theblasfrompas.blogspot.com.au/2016/12/spring-boot-feign-client-accessing.html" was to use Twitter Typeahead for auto completion which is the demo on Github below. The returned data is now used in an INPUT text input for auto completion as the user types in the Country Name

https://github.com/papicella/FeignClientExternalSpringBoot


Friday, 9 December 2016

Spring Boot / Feign Client accessing external service

Previously we used Feign to create clients for our own services, which are registered on our Eureka Server using a service name as shown in the previous blog post http://theblasfrompas.blogspot.com.au/2016/11/declarative-rest-client-feign-with_8.html. It's not unusual that you'd want to implement an external rest endpoint, basically an endpoint that's not discoverable by Eureka. In that case, you can use the url property on the @FeignClient annotation,
which gracefully supports property injection. His an example of this.

Full example on GitHub as follows

https://github.com/papicella/FeignClientExternalSpringBoot

1. Start by adding the correct maven dependencies and the one you need is as follows, there would be others if you want to use a web based spring boot project etc.
  
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-feign</artifactId>
</dependency>

2. We are going to consume this external service as follows

http://country.io/names.json

To do that we create a simple interface as follows
  
package pas.au.pivotal.feign.external;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@FeignClient(name = "country-service-client", url = "http://country.io")
public interface CountryServiceClient {

    @RequestMapping(method = RequestMethod.GET, value = "/names.json")
    String getCountries();
}

3. In this example I have created a RestController to consume this REST service and test it because it's the easiest way to do this. We simply AutoWire the CountryServiceClient interface into the RestController to make those external calls through FEIGN.
  
package pas.au.pivotal.feign.external.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import pas.au.pivotal.feign.external.CountryServiceClient;

import java.util.Map;

@RestController
public class CountryRest
{
    Logger logger = LoggerFactory.getLogger(CountryRest.class);
    private static final JsonParser parser = JsonParserFactory.getJsonParser();

    @Autowired
    private CountryServiceClient countryServiceClient;

    @RequestMapping(value = "/countries", method = RequestMethod.GET, 
                         produces = "application/json")
    public String allCountries()
    {
        String countries = countryServiceClient.getCountries();

        return countries;
    }

    @RequestMapping(value = "/country_names", method = RequestMethod.GET)
    public String[] countryNames ()
    {
        String countries = countryServiceClient.getCountries();

        Map<String, Object> countryMap = parser.parseMap(countries);

        String countryArray[] = new String[countryMap.size()];
        logger.info("Size of countries " + countryArray.length);

        int i = 0;
        for (Map.Entry<String, Object> entry : countryMap.entrySet()) {
            countryArray[i] = (String) entry.getValue();
            i++;
        }

        return countryArray;

    }
}

4. Of course we will have our main class to boot strap the application and it includes the "spring-boot-starter-web" maven repo to start a tomcat server for us.
  
package pas.au.pivotal.feign.external;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.feign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients
public class FeignClientExternalSpringBootApplication {

 public static void main(String[] args) {
  SpringApplication.run(FeignClientExternalSpringBootApplication.class, args);
 }
} 

5. Ensure your application.properties or application.yml has the following properties to disable timeouts.

feign:
  hystrix:
    enabled: false

hystrix:
  command:
    choose:
      default:
        execution:
          timeout:
            enabled: false

6. Run the main class "FeignClientExternalSpringBootApplication"

Access as follows

http://localhost:8080/countries