Get familiar with the Spring MVC and the Spring Boot Web applications.

Time: 30 minutes.

Code Snippet Manager

As developers normally we require to have some code snippets that help us to code fast and safe.

The main idea of this application is to create a site that can manage our Code Snippets. We are going to create a RESTful API for any external client.

  1. Open a browser and hit the url: http://start.spring.io

  2. Click the Switch to the full version link.

  3. Fill out the Code Snippet Manager Project metadata with (See Figure 1.0):

    Table 1. Code Snippet Manager App - metadata
    Property Value

    Group:

    io.pivotal.workshop

    Artifact:

    code-snippet-manager

    Name:

    code-snippet-manager

    Package Name:

    io.pivotal.workshop.snippet

    Dependencies:

    Web, DevTools, Groovy Templates

    Spring Boot:

    2.0.0.M7

    Figure 1.0: Spring Initializr - http://start.spring.io/

    SpringInitializr

    Tip
    You can choose either Maven or Gradle project types.
  4. Type Web, DevTools, Groovy Templates, in the Dependencies field and press Enter.

  5. Click the Generate Project button.

  6. Unzip the file in any directory you want.

  7. Import your project in any IDE you want.

  8. Let’s start by defining the domain models. Create the following classes:

    io.pivotal.workshop.snippet.domain.Language.java
    package io.pivotal.workshop.snippet.domain;
    
    public class Language {
    
            private String id;
            private String name;
            private String syntax = "text";
    
            public Language() {
                    this.id = java.util.UUID.randomUUID().toString().replaceAll("-", "");;
            }
    
            public Language(String name) {
                    this();
                    this.name = name;
            }
    
            public Language(String name, String syntax) {
                    this(name);
                    this.syntax = syntax;
            }
    
            public String getId() {
                    return id;
            }
    
            public void setId(String id) {
                    this.id = id;
            }
    
            public String getName() {
                    return name;
            }
    
            public void setName(String name) {
                    this.name = name;
            }
    
            public String getSyntax() {
                    return syntax;
            }
    
            public void setSyntax(String syntax) {
                    this.syntax = syntax;
            }
    }

    We can have multiple snippets in different programming languages, that’s why we have this class. Take a look that also there is a syntax field; this field will be use later on for syntax highlight.

    io.pivotal.workshop.snippet.domain.Code.java
    package io.pivotal.workshop.snippet.domain;
    
    public class Code {
            private String id;
            private String source;
    
            public Code() {
                    this.id = java.util.UUID.randomUUID().toString().replaceAll("-", "");;
            }
    
            public Code(String source) {
                    this();
                    this.source = source;
            }
    
            public String getId() {
                    return id;
            }
    
            public void setId(String id) {
                    this.id = id;
            }
    
            public String getSource() {
                    return source;
            }
    
            public void setSource(String source) {
                    this.source = source;
            }
    
    }

    This class will be use to hold the actual snippet code.

    io.pivotal.workshop.snippet.domain.Snippet.java
    package io.pivotal.workshop.snippet.domain;
    
    import java.util.Date;
    
    public class Snippet {
    
            private String id;
            private String title;
            private String keywords;
            private String description;
            private Language lang;
            private Code code;
            private Date created;
            private Date modified;
    
            public Snippet() {
                    this.id = java.util.UUID.randomUUID().toString().replaceAll("-", "");
                    this.created = new Date();
                    this.modified = new Date();
            }
    
            public Snippet(String title, String keywords, String description, Language lang, Code code) {
                    this();
                    this.title = title;
                    this.keywords = keywords;
                    this.description = description;
                    this.lang = lang;
                    this.code = code;
            }
    
            public Snippet(String title, Language lang, Code code) {
                    this(title, "", "", lang, code);
            }
    
            public String getId() {
                    return id;
            }
    
            public void setId(String id) {
                    this.id = id;
            }
    
            public String getTitle() {
                    return title;
            }
    
            public void setTitle(String title) {
                    this.title = title;
            }
    
            public Language getLang() {
                    return lang;
            }
    
            public void setLang(Language lang) {
                    this.lang = lang;
            }
    
            public String getDescription() {
                    return description;
            }
    
            public void setDescription(String description) {
                    this.description = description;
            }
    
            public String getKeywords() {
                    return keywords;
            }
    
            public void setKeywords(String keywords) {
                    this.keywords = keywords;
            }
    
            public Code getCode() {
                    return code;
            }
    
            public void setCode(Code code) {
                    this.code = code;
            }
    
            public Date getCreated() {
                    return created;
            }
    
            public void setCreated(Date created) {
                    this.created = created;
            }
    
            public Date getModified() {
                    return modified;
            }
    
            public void setModified(Date modified) {
                    this.modified = modified;
            }
    }

    The above class will be the main reponse.

  9. Next let’s create a base interface that will be use as main repository. In this case we are going to hold all the data in Memory using collections:

    io.pivotal.workshop.snippet.repository.SimpleRepository.java
    package io.pivotal.workshop.snippet.repository;
    
    import java.util.Collection;
    
    public interface SimpleRepository<T> {
    
            Iterable<T> findAll();
            void saveAll(Collection<T> items);
            T saveAll(T item);
            T findById(String id);
    }

    as you can see it has just the most common actions.

  10. Let’s create now all the Repositories that will be implementing the SimpleRepository interface.

    io.pivotal.workshop.snippet.repository.LanguageRepository.java
    package io.pivotal.workshop.snippet.repository;
    
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.List;
    import java.util.Optional;
    import java.util.stream.StreamSupport;
    
    import org.springframework.stereotype.Repository;
    
    import io.pivotal.workshop.snippet.domain.Language;
    
    @Repository
    public class LanguageRepository implements SimpleRepository<Language>{
    
        private List<Language> languages = new ArrayList<>();
    
            public Iterable<Language> findAll(){
                    return languages;
            }
    
            public void saveAll(Collection<Language> languages){
                    this.languages.addAll(languages);
            }
    
            public Language findById(String name) {
                    Optional<Language> language = StreamSupport
                            .stream(this.languages.spliterator(), false)
                            .filter(lang -> lang.getName().equals(name))
                            .findFirst();
    
                    if (language.isPresent()) return language.get();
    
                    return null;
            }
    
            public Language saveAll(Language item) {
                    assert item.getName() != null;
    
                    Language language = this.findById(item.getName());
    
                    if(language == null) {
                            this.languages.add(item);
                            return item;
                    }else {
                            language.setName(item.getName());
                            language.setSyntax(item.getSyntax());
                            return language;
                    }
            }
    }
    io.pivotal.workshop.snippet.repository.CodeRepository.java
    package io.pivotal.workshop.snippet.repository;
    
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.List;
    import java.util.Optional;
    
    import org.springframework.stereotype.Repository;
    
    import io.pivotal.workshop.snippet.domain.Code;
    
    @Repository
    public class CodeRepository implements SimpleRepository<Code>{
    
            private List<Code> codes = new ArrayList<>();
    
            public List<Code> findAll() {
                    return codes;
            }
    
            @Override
            public void saveAll(Collection<Code> items) {
                    this.codes.addAll(items);
            }
    
            @Override
            public Code saveAll(Code item) {
                    assert item.getSource() != null;
    
                    Code code = findById(item.getId());
    
                    if(code == null){
                            this.codes.add(item);
                            return item;
                    }
                    else {
                            code.setSource(item.getSource());
                            return code;
                    }
            }
    
            @Override
            public Code findById(String id) {
                    Optional<Code> code = codes
                                    .stream()
                                    .filter(c -> c.getId().equals(id))
                                    .findFirst();
                    if (code.isPresent()) return code.get();
    
                    return null;
            }
    
    
    }
    io.pivotal.workshop.snippet.repository.SnippetRepository.java
    package io.pivotal.workshop.snippet.repository;
    
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Date;
    import java.util.List;
    import java.util.Optional;
    
    import org.springframework.stereotype.Repository;
    
    import io.pivotal.workshop.snippet.domain.Snippet;
    
    @Repository
    public class SnippetRepository implements SimpleRepository<Snippet>{
    
            private List<Snippet> snippets = new ArrayList<>();
    
            @Override
            public Snippet saveAll(Snippet snippet){
                    assert snippet.getTitle() != null;
                    assert snippet.getCode() != null;
                    assert snippet.getLang() != null;
    
                    Snippet _snippet =  null;
    
                    if (snippet.getId() == null) {
                            _snippet = new Snippet(snippet.getTitle(), snippet.getLang(),snippet.getCode());
    
                    } else {
                             _snippet = this.findById(snippet.getId());
                             if(_snippet != null){
                                     _snippet.setTitle(snippet.getTitle());
                                     _snippet.setCode(snippet.getCode());
                                     _snippet.setLang(snippet.getLang());
                                     _snippet.setModified(new Date());
                             }
                    }
    
                    return _snippet;
            }
    
            @Override
            public Iterable<Snippet> findAll(){
                    return this.snippets;
            }
    
            @Override
            public Snippet findById(String id){
                    Optional<Snippet> result = snippets.stream()
                                    .filter(snippet -> snippet.getId().equals(id))
                                    .findFirst();
    
                    if (result.isPresent()) return result.get();
    
                    return null;
            }
    
            @Override
            public void saveAll(Collection<Snippet> items) {
                    this.snippets.addAll(items);
            }
    }
  11. Next lets do the main controller that will expose the REST API:

    io.pivotal.workshop.snippet.controller.SnippetController.java
    @RestController
    public class SnippetController {
    
    
            private SnippetRepository snippetRepository;
            private LanguageRepository languageRepository;
    
            public SnippetController(SnippetRepository snippetRepository,LanguageRepository languageRepository){
                    this.snippetRepository = snippetRepository;
                    this.languageRepository = languageRepository;
            }
    
            @RequestMapping("/")
            public ModelAndView home(){
                    assert snippetRepository != null;
    
                    Map<String,Object> model = new HashMap<String,Object>();
                    model.put("langs", languageRepository.findAll());
                    model.put("snippets", snippetRepository.findAll());
    
                    return new ModelAndView("views/home",model);
            }
    
            @RequestMapping("/snippets")
            public ResponseEntity<?> snippets(){
                    assert snippetRepository != null;
                    return ResponseEntity.ok(snippetRepository.findAll());
            }
    }
  12. Create a configuration class to initialize your repositories:

    io.pivotal.workshop.snippet.config.SnippetConfiguration.java
    package io.pivotal.workshop.snippet.config;
    
    import java.nio.file.Files;
    import java.nio.file.Paths;
    import java.util.ArrayList;
    import java.util.List;
    
    import org.springframework.boot.CommandLineRunner;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import io.pivotal.workshop.snippet.domain.Code;
    import io.pivotal.workshop.snippet.domain.Language;
    import io.pivotal.workshop.snippet.domain.Snippet;
    import io.pivotal.workshop.snippet.repository.SnippetRepository;
    
    @Configuration
    public class SnippetConfiguration {
    
            @Bean
            public CommandLineRunner runner(SnippetRepository snippetRepo) {
                    return args -> {
                            @SuppressWarnings("serial")
                            List<Snippet> snippets = new ArrayList<Snippet>() {
                                    {
                                            add(new Snippet("Hello World", new Language("HTML", "xml"),new Code(new String(Files.readAllBytes(Paths.get("code/html-code.txt"))))));
                                            add(new Snippet("Hello World", new Language("C#", "c#"),new Code(new String(Files.readAllBytes(Paths.get("code/cs-code.txt"))))));
                                            add(new Snippet("Hello World", new Language("Pascal", "py"),new Code(new String(Files.readAllBytes(Paths.get("code/pas-code.txt"))))));
                                            add(new Snippet("Hello World", new Language("Erlang", "erl"),new Code(new String(Files.readAllBytes(Paths.get("code/erl-code.txt"))))));
                                            add(new Snippet("Hello World", new Language("JavaScript", "js"),new Code(new String(Files.readAllBytes(Paths.get("code/js-code.txt"))))));
                                            add(new Snippet("Hello World", new Language("Groovy", "groovy"),new Code("println 'Hello World'")));
                                    }
                            };
    
                            snippetRepo.saveAll(snippets);
    
                    };
            }
    }

    as you can see from the above code, we are building up our data.

    If you take a close look, we are using the Files.readAllBytes helper class to read from a file, and this file is located in the code/ folder.

  13. Add some code snippets in the code/ folder.

    code/html-code.txt - HTML
    <html>
      <body>
        <h1>Hello World</h1>
      </body>
    </html>
    code/cs-code.txt - C#
    class Hello {
     static void Main() {
      System.Console.Write("Hello World");
     }
    }
    code/pas-code.txt - Pascal
    program HelloWorld;
    
    begin
      writeln('Hello World');
    end.
    code/erl-code.txt - Erlang
     -module(hello).
     -export([hello_world/0]).
    
     hello_world() -> io:fwrite("hello, world\n").
    code/js-code.txt - JavaScript
    console.log("Hello World");
  14. A lot of code, right? Well now its time to run the application. You can use your IDE or command line. Once running you application, go to your browser and hit: http://localhost:8080/snippets and you should get the a response like Figure 2.0.

    Figure 2.0: Snippet Code Manager - http://localhost:8080/snippets

    /snippets

Challenges

Adding a UI - Home Page

You can see that the io.pivotal.workshop.snippet.controller.SnippetController.java has the home() method, and it returns the view: views/home and the model, a map that contains the languages and the snippets: Add the following missing dependencies to your pom.xml because you will need them:

<dependency>
    <groupId>org.webjars</groupId>
        <artifactId>jquery</artifactId>
        <version>2.2.4</version>
</dependency>
<dependency>
        <groupId>org.webjars</groupId>
        <artifactId>bootstrap</artifactId>
        <version>3.3.6</version>
</dependency>
<dependency>
        <groupId>org.webjars</groupId>
        <artifactId>angularjs</artifactId>
        <version>1.5.7</version>
</dependency>

Add the following libraries in the src/main/resources/static/css: theme.css and offcanvas.css and in the src/main/resources/static/js folder add the syntaxhighlighter.js script.

  • Create the necessary layout and home page to have the same as Figure 3.0.

    Figure 3.0: Code Snippet Manager - http://localhost:8080

    /

REST API

  • Complete the RESTful API:

    • provide the /snippets/{id} path endpoint to search by snippet Id.

    • provide methods to handle the: POST, PUT for adding a new snippet and updating the snippet.

  • Make changes to repose either JSON or XML.

  • When doing a XML response, the snippet code should be in a CDATA tag. Modify the code to show a CDATA for every source code. (tip: @JacksonXmlCData)

[HOMEWORK]

  • Add validation to the POST and PUT methods (tip: @Valid).

  • Finish up any missing UI or Controller methods.