Editor’s note: We found an interesting series of articlesLearn 30 New Technologies in 30 Days, is being translated, an update a day, year-end gift package. Here’s day 22.


For today’s Learn 30 New Technologies in 30 Days challenge, I decided to develop a single-page application using the Spring Framework, MongoDB, and AngularJS. I’m familiar with Spring and MongoDB, but I haven’t used AngularJS in conjunction with Spring. Today we are going to develop a social bookmarking application similar to the one we developed a few days ago with EmberJS. I introduced the basics of AngularJS the next day, so please see my article for more information. This article uses the latest version of the Spring framework, 3.2.5.Release, and we will not use XML (not even Web.xml). We’ll configure everything using Spring’s annotation support. We will use Spring MVC to create a REST backend. At the same time AngularJS as the client MVC framework to develop the front end of the application.

application

We will develop a social bookmarking application that allows users to submit and share links. You can check out the app here. This app does:

  • When the user accesses /, he sees a list of stories sorted by the time they were submitted.

  • When a user access a bookmark, # / stories / 528 b9a8ce4b0da0473622359, for example, the user will see the information about the report, who is submitted, for example, when to submit, and a summary of the article. AngularJS will send a restful GET requests (/ API/v1 / stories / 528 b9a8ce4b0da0473622359) to obtain the full text.

  • Finally, when a user submits a new story via #/stories/new, a POST request is made to the REST back end and the story is saved in the MongoDB database. Users only need to fill in the URL of the story. The application will use the Goose Extractor RESTful API we developed on day 16 to get the title, main image and summary of the article,

The premise

  1. Basic Java knowledge. Install the latest JDK. You can install both OpenJDK 7 and Oracle JDK 7. OpenShift supports OpenJDK6 and 7.

  2. Basic Spring knowledge.

  3. Sign up for an OpenShift account. Registration is completely free, and Red Hat gives each user three free Gear sets that you can use to run your app. At the time of this writing, each user gets a total of 1.5 GB of RAM and 3 GB of hard disk space for free.

  4. Install the RHC client tool. RHC is a Ruby gem, so you need Ruby 1.8.7 or higher on your machine. Simply type sudo gem install RHC to install RHC. If you’ve already installed it, make sure it’s the latest version. Run sudo gem update RHC to upgrade. Detailed information on configuring RHC command-line tool, please refer to: https://openshift.redhat.com/community/developers/rhc-client-tools-install

  5. Use RHC’s setup command to configure your OpenShift account. This command will help you create a namespace and upload your SSH public key to the OpenShift server.

Making the warehouse

The code for today’s example application is available on GitHub.

The first step is to create the Tomcat 7 application

Create a new application using Tomcat 7 and MongoDB

rhc create-app getbookmarks tomcat-7 mongodb-2

This will create an application container for us, Gear, set up the public DNS, create a private Git repository, and deploy the application using code from your GitHub repository. Applications can be accessed at http://getbookmarks-{domain-name}.rhcloud.com/. Replace {domain-name} with your own OpenShift domain (domain names are sometimes referred to as namespaces).

Step 2: Remove the template code

Next we delete the template code created by OpenShift

cd getbookmarks
git rm -rf src/main/webapp/*.jsp src/main/webapp/index.html src/main/webapp/images src/main/webapp/WEB-INF/web.xml  
git commit -am "deleted template files"

Note that we have also removed web.xml.

The third step is to update pom.xml

Replace the contents of the pom.xml with the following code

The < project XMLNS = "http://maven.apache.org/POM/4.0.0" XMLNS: xsi = "http://www.w3.org/2001/XMLSchema-instance" Xsi: schemaLocation = "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd" > The < modelVersion > 4.0.0 < / modelVersion > < groupId > getbookmarks < / groupId > < artifactId > getbookmarks < / artifactId > < packaging > war < / packaging > < version > 1.0 < / version > < name > getbookmarks < / name > < properties > < project. Build. SourceEncoding > utf-8 < / project. Build. SourceEncoding > < maven.com piler. Source > 1.7 < / maven.com piler source > < maven.com piler target > 1.7 < / maven.com piler. Target > < / properties > < dependencies > < the dependency > < the groupId > org. Springframework < / groupId > < artifactId > spring - webmvc < / artifactId > < version > 3.2.5. RELEASE < / version > </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> < version > 3.2.5. RELEASE < / version > < / dependency > < the dependency > < groupId > org. Springframework. Data < / groupId > < artifactId > spring - data - directing a < / artifactId > < version > 1.3.2. RELEASE < / version > < / dependency > < the dependency > < the groupId > org. Codehaus. Jackson < / groupId > < artifactId > Jackson - mapper - asl < / artifactId > < version > 1.9.13 < / version > </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> >3.1.0</version> </scope> Provided </scope> </ Dependencies > < Profiles > <profile> < ID >openshift</ ID > <build> <finalName>getbookmarks</finalName> <plugins> <plugin> <artifactId>maven-war-plugin</artifactId> The < version > 2.4 < / version > ` < configuration > < failOnMissingWebXml > false < / failOnMissingWebXml > <outputDirectory>webapps</outputDirectory> <warName>ROOT</warName> </configuration> </plugin> </plugins> </build> </profile> </profiles> </project>

In the pom.xml above:

  1. We have added Maven dependencies for Spring-WebMVC, Spring-MongoDB, Jackson, and the latest Servlet API.
  2. The project uses JDK 7 instead of JDK 6.
  3. Use the latest Maven WAR plugin. In order to avoidweb.xmlThere are no resulting build errors, we added a configuration option.

After the changes, don’t forget to right-click Maven > Update Project to Update the Maven Project.

Step 4 WriteWebMvcConfigAppConfig

Create the com. getBookmarks. Config package and the WebMVCconfig class.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.json.MappingJacksonJsonView;

@EnableWebMvc
@ComponentScan(basePackageClasses = StoryResource.class)
@Configuration
public class WebMvcConfig{

}

Next we will create the AppConfig configuration class. Spring MongoDB has a repository concept where you implement an interface, and Spring generates the corresponding proxy classes. This makes writing warehouse classes very easy and saves a lot of boilerplate code. Spring MongoDB allows us to activate the Mongo repository with the @EnableMongoRepositories annotation declaration.

import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.authentication.UserCredentials; import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.SimpleMongoDbFactory; import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; import com.getbookmarks.repository.StoryRepository; import com.mongodb.Mongo; @Configuration @EnableMongoRepositories(basePackageClasses = StoryRepository.class) public class ApplicationConfig { @Bean public MongoTemplate mongoTemplate() throws Exception { String openshiftMongoDbHost = System.getenv("OPENSHIFT_MONGODB_DB_HOST"); ` int openshiftMongoDbPort = Integer.parseInt(System.getenv("OPENSHIFT_MONGODB_DB_PORT")); String username = System.getenv("OPENSHIFT_MONGODB_DB_USERNAME"); String password = System.getenv("OPENSHIFT_MONGODB_DB_PASSWORD"); Mongo mongo = new Mongo(openshiftMongoDbHost, openshiftMongoDbPort); UserCredentials userCredentials = new UserCredentials(username, password); String databaseName = System.getenv("OPENSHIFT_APP_NAME"); MongoDbFactory mongoDbFactory = new SimpleMongoDbFactory(mongo, databaseName, userCredentials); MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory); return mongoTemplate; }}

Step 5 write GetBookmarksWebApplicationInitializer class

Under Servlet 3.0, web.xml is optional. Usually, we in the web. In the XML configuration Spring WebMVC scheduler, but since Spring 3.1, we can use WebApplicationInitializer programmatically configuration. Spring provides an implementation of SpringServletContainerInitializer ServletContainerInitializer interface. SpringServletContainerInitializer class delegate tasks to offer org. Springframework. Web. WebApplicationInitializer implementation. You only need to implement a method WebApplicationInitializer# onStartup (ServletContext), transfer need to initialize the ServletContext.

import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRegistration.Dynamic; import org.springframework.web.WebApplicationInitializer; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.servlet.DispatcherServlet; public class GetBookmarksWebApplicationInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletContext) throws ServletException { AnnotationConfigWebApplicationContext webApplicationContext = new AnnotationConfigWebApplicationContext(); webApplicationContext.register(ApplicationConfig.class, WebMvcConfig.class); Dynamic dynamc = servletContext.addServlet("dispatcherServlet", new DispatcherServlet(webApplicationContext)); dynamc.addMapping("/api/v1/*"); dynamc.setLoadOnStartup(1); }}

Step 6 Create the Story Domain class

In this application, we only have one Story Domain class.

@Document(collection = "stories") public class Story { @Id private String id; private String title; private String text; private String url; private String fullname; private final Date submittedOn = new Date(); private String image; Public Story() {} // For simplicity, remove the Getter and Setter

There are two notable things in the above code:

  1. @DocumentThe annotation specifies the domain objects that will be persisted in MongoDB.storiesSpecifies the name of the collection to be created in MongoDB.
  2. @IdMark this field as an ID and the corresponding field will be automatically generated by MongoDB.

Step 7 Create StoryRepository

As mentioned before,. Spring MongoDB has a repository concept where you implement an interface, and Spring generates the corresponding proxy classes. Let’s create a StoryRepository as shown below.

import java.util.List;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import com.getbookmarks.domain.Story;

@Repository
public interface StoryRepository extends CrudRepository<Story, String> {

    public List<Story> findAll();
}

What is notable in the above code is:

  1. The StoryRepository interface extends the CrudRepository interface, which defines CRUD and Finder methods. So Spring-generated proxies will have these methods.
  2. @RepositoryIt’s a special one@ComponentAnnotation, indicating that the class is a repository or DAO class. Cooperate withPersistenceExceptionTranslationPostProcessorWhen used, there is@epositoryClass can be Spring’sDataAccessExceptionConversion.

Step 8: Write StoryResource

Next, we’ll write the REST JSON Web service that performs the creation and reading of the report. Let’s create a Spring MVC controller with the following methods:

@Controller
@RequestMapping("/stories")
public class StoryResource {

    private StoryRepository storyRepository;

    @Autowired
    public StoryResource(StoryRepository storyRepository) {
        this.storyRepository = storyRepository;
    }

    @RequestMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public ResponseEntity<Void> submitStory(@RequestBody Story story) {
        Story storyWithExtractedInformation = decorateWithInformation(story);
        storyRepository.save(storyWithExtractedInformation);
        ResponseEntity<Void> responseEntity = new ResponseEntity<>(HttpStatus.CREATED);
        return responseEntity;
    }

    @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public List<Story> allStories() {
        return storyRepository.findAll();
    }

    @RequestMapping(value = "/{storyId}", produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public Story showStory(@PathVariable("storyId") String storyId) {
        Story story = storyRepository.findOne(storyId);
        if (story == null) {
            throw new StoryNotFoundException(storyId);
        }
        return story;
    }

    private Story decorateWithInformation(Story story) {
        String url = story.getUrl();
        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<Story> forEntity = restTemplate.getForEntity(
                "http://gooseextractor-t20.rhcloud.com/api/v1/extract?url=" + url, Story.class);
        if (forEntity.hasBody()) {
            return new Story(story, forEntity.getBody());
        }
        return story;

    }

}

Step 9: Configure AngularJS and Twitter Bootstrap

Download the latest version of AngularJS and Bootstrap from the official website. Alternatively, you can copy it from the project’s GitHub repository.

Step 10 Create index.html

Now we’re going to write the page for our application

<! DOCTYPE html> <html ng-app="getbookmarks"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8"/> <link rel="stylesheet" href="css/bootstrap.css"/> <link rel="stylesheet" href="css/toastr.css"/> <style> body { padding-top: 60px; } </style> <title>GetBookmarks : Submit Story</title> </head> <body> <div class="navbar navbar-inverse navbar-fixed-top"> <div class="navbar-inner"> <div  class="container"> <button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse"> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="brand" href="#">GetBookmarks</a> </div> </div> </div> <div class="container" ng-view> </div> <script SRC ="js/jquery-1.9.1.js"></script> <script SRC ="js/bootstrap.js"></script> <script SRC ="js/angular.js"></script> <script  src="js/angular-resource.js"></script> <script src="js/toastr.js"></script> <script src="js/app.js"></script> </body> </html>

In the HTML code shown above:

  1. We import the libraries we need. Our application code is in app.js.

  2. In Angular, we use the ng-app directive to define the scope of a project. In the code above, we added ng-app to the HTML element, which we can use in virtually any element. Using the ng-app directive in an HTML element means that AngularJS is available throughout index.html. The ng-app directive can specify a name. This name is the name of the module. I used getBookmarks as the module name for the application.

  3. The ng-view directive is used in index.html. This directive renders the template corresponding to the route in index.html. So, every time you access a route, only the ng-view area changes.

Step 11: Write AngularJS code

Js contains all the application-related code. All application routes are defined. In the following code, we’ll define the three routes and the corresponding Angular controller.

angular.module("getbookmarks.services", ["ngResource"]).
    factory('Story', function ($resource) {
        var Story = $resource('/api/v1/stories/:storyId', {storyId: '@id'});
        Story.prototype.isNew = function(){
            return (typeof(this.id) === 'undefined');
        }
        return Story;
    });

angular.module("getbookmarks", ["getbookmarks.services"]).
  ` config(function ($routeProvider) {
        $routeProvider
            .when('/', {templateUrl: 'views/stories/list.html', controller: StoryListController})
            .when('/stories/new', {templateUrl: 'views/stories/create.html', controller: StoryCreateController})
            .when('/stories/:storyId', {templateUrl: 'views/stories/detail.html', controller: StoryDetailController});
    });

function StoryListController($scope, Story) {
    $scope.stories = Story.query();

}

function StoryCreateController($scope, $routeParams, $location, Story) {

    $scope.story = new Story();

    $scope.save = function () {
        $scope.story.$save(function (story, headers) {
            toastr.success("Submitted New Story");
            $location.path('/');
        });
    };
}


function StoryDetailController($scope, $routeParams, $location, Story) {
    var storyId = $routeParams.storyId;

    $scope.story = Story.get({storyId: storyId});

}

Step 11: Deploy the code

Finally, submit the code and push it to App Gear.

git add .
git commit -am "application code"
git push

That’s all for today. Feedback is welcome.


Day 22: Developing Single Page Applications with Spring, MongoDB, and AngularJS SegmentFault