Saturday, 9 April 2016

SQL Graph Database Using Continued Fractions

This post is a continuation of a previous post (Continued Fraction Database File System) which used a SQL RDBMS to implement a file system tree using continued fractions. If you want to understand the maths behind the project, read that previous post first and the paper by Dan Hazel which it is based on, Using rational numbers to key nested sets.

In the previous post, each node had one path, and so could only represent trees. In a graph, each node can have multiple paths. Each path can be represented by a list of integers, where each integer is the index of the child beneath it's parent node, for example:

The integer list paths can be used in a continued fraction to calculate a real number value to be used as the primary key for a node path relation. So our database schema looks like this:

This model could be extended with a properties relation to store multiple key-value pairs for each node and path.

The JPA entities for the node and path relations are:

Node.java

package org.adrianwalker.continuedfractions.graph.entity;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import javax.persistence.Basic;
import static javax.persistence.CascadeType.ALL;
import javax.persistence.Column;
import javax.persistence.Entity;
import static javax.persistence.FetchType.LAZY;
import javax.persistence.GeneratedValue;
import static javax.persistence.GenerationType.IDENTITY;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Entity
@Table(name = "node")
@NamedQueries({
  @NamedQuery(name = "tree",
      query = "SELECT DISTINCT n2 "
      + "FROM Node n1, Node n2, NodePath np1, NodePath np2 "
      + "WHERE n1.id = np1.node.id "
      + "AND (np2.id >= np1.id AND np2.id < np1.sid) "
      + "AND n2.id = np2.node.id "
      + "AND n1.id = :id "
      + "ORDER BY n2.id"
  ),
  @NamedQuery(name = "parents",
      query = "SELECT DISTINCT n2 "
      + "FROM Node n1, Node n2, NodePath np1, NodePath np2 "
      + "WHERE n1.id = np1.node.id "
      + "AND n2.id = np2.node.id "
      + "AND (np1.id > np2.id AND np1.id < np2.sid) "
      + "AND np2.hops = np1.hops - 1 "
      + "AND n1.id = :id "
      + "ORDER BY np2.id"
  ),
  @NamedQuery(name = "children",
      query = "SELECT DISTINCT n2 "
      + "FROM Node n1, Node n2, NodePath np1, NodePath np2 "
      + "WHERE n1.id = np1.node.id "
      + "AND n2.id = np2.node.id "
      + "AND (np2.id > np1.id AND np2.id < np1.sid) "
      + "AND np2.hops = np1.hops + 1 "
      + "AND n1.id = :id "
      + "ORDER BY np2.id"
  )})
public class Node implements Serializable {

  private static final long serialVersionUID = 1L;

  @Id
  @GeneratedValue(strategy = IDENTITY)
  @Basic(optional = false)
  @Column(name = "id", nullable = false)
  private Long id;

  @Basic(optional = false)
  @Column(name = "name", nullable = false)
  private String name;

  @OneToMany(fetch = LAZY, cascade = ALL, orphanRemoval = true, mappedBy = "node")
  private List<NodePath> nodePaths;

  public Node() {
  }

  public Long getId() {
    return id;
  }

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

  public String getName() {
    return name;
  }

  public void setName(final String name) {
    this.name = name;
  }

  public List<NodePath> getNodePaths() {

    if (null == nodePaths) {
      nodePaths = new ArrayList<>();
    }

    return nodePaths;
  }

  public void setNodePaths(final List<NodePath> nodePaths) {
    this.nodePaths = nodePaths;
  }

  @Override
  public int hashCode() {

    int hash = 5;
    hash = 89 * hash + Objects.hashCode(this.id);

    return hash;
  }

  @Override
  public boolean equals(final Object obj) {

    if (this == obj) {
      return true;
    }

    if (obj == null) {
      return false;
    }

    if (getClass() != obj.getClass()) {
      return false;
    }

    return Objects.equals(this.id, ((Node) obj).id);
  }

  @Override
  public String toString() {
    return "Node{" + "id=" + id + ", name=" + name + '}';
  }
}

NodePath.java

package org.adrianwalker.continuedfractions.graph.entity;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Objects;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import org.adrianwalker.continuedfractions.Fraction;

@Entity
@Table(name = "node_path",
    uniqueConstraints = {
      @UniqueConstraint(columnNames = {"id", "sid"})
    },
    indexes = {
      @Index(columnList = "hops", unique = false)
    })
public class NodePath implements Serializable {

  private static final long serialVersionUID = 1L;

  @Id
  @Basic(optional = false)
  @Column(name = "id", nullable = false, precision = (Fraction.SCALE * 2) - 1, scale = Fraction.SCALE)
  private BigDecimal id;

  @Basic(optional = false)
  @Column(name = "sid", nullable = false, precision = (Fraction.SCALE * 2) - 1, scale = Fraction.SCALE)
  private BigDecimal sid;

  @Basic(optional = false)
  @Column(name = "hops", nullable = false)
  private Integer hops;

  @ManyToOne(optional = false)
  @JoinColumn(name = "node_id", referencedColumnName = "id", nullable = false)
  private Node node;

  public NodePath() {
  }

  public BigDecimal getId() {
    return id;
  }

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

  public BigDecimal getSid() {
    return sid;
  }

  public void setSid(final BigDecimal sid) {
    this.sid = sid;
  }

  public Integer getHops() {
    return hops;
  }

  public void setHops(final Integer hops) {
    this.hops = hops;
  }

  public Node getNode() {
    return node;
  }

  public void setNode(final Node node) {
    this.node = node;
  }

  @Override
  public int hashCode() {

    int hash = 3;
    hash = 41 * hash + Objects.hashCode(this.id);

    return hash;
  }

  @Override
  public boolean equals(final Object obj) {

    if (this == obj) {
      return true;
    }

    if (obj == null) {
      return false;
    }

    if (getClass() != obj.getClass()) {
      return false;
    }

    return Objects.equals(this.id, ((NodePath) obj).id);
  }

  @Override
  public String toString() {
    return "NodePath{" + "id=" + id + ", sid=" + sid + ", hops=" + hops + '}';
  }
And a simple JPA controller:

Controller.java

package org.adrianwalker.continuedfractions.graph.controller;

import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Query;
import org.adrianwalker.continuedfractions.graph.entity.Node;
import org.adrianwalker.continuedfractions.graph.entity.NodePath;

public final class Controller {

  private final EntityManagerFactory emf;

  public Controller(final EntityManagerFactory emf) {
    this.emf = emf;
  }

  public EntityManager getEntityManager() {
    return emf.createEntityManager();
  }

  public Node create(final Node node) throws Exception {

    EntityManager em = getEntityManager();

    try {
      begin(em);
      em.persist(node);
      end(em);
    } finally {
      em.close();
    }

    return node;
  }

  public NodePath create(final NodePath nodePath) throws Exception {

    EntityManager em = getEntityManager();

    try {
      begin(em);
      em.persist(nodePath);
      end(em);
    } finally {
      em.close();
    }

    return nodePath;
  }

  public List<Node> tree(final Node node) {

    EntityManager em = getEntityManager();
    Query tree = em.createNamedQuery("tree");
    tree.setParameter("id", node.getId());

    try {
      return tree.getResultList();
    } finally {
      em.close();
    }
  }

  public List<Node> parents(final Node node) {

    EntityManager em = getEntityManager();
    Query children = em.createNamedQuery("parents");
    children.setParameter("id", node.getId());

    try {
      return children.getResultList();
    } finally {
      em.close();
    }
  }

  public List<Node> children(final Node node) {

    EntityManager em = getEntityManager();
    Query children = em.createNamedQuery("children");
    children.setParameter("id", node.getId());

    try {
      return children.getResultList();
    } finally {
      em.close();
    }
  }

  private void begin(final EntityManager em) {
    em.getTransaction().begin();
  }

  private void end(final EntityManager em) {
    em.getTransaction().commit();
  }
}

The Graph class calls the controller to persist Node and NodePath entities. It has methods to add nodes and node paths, to list all the nodes beneath a given node, and methods to get the immediate parents and children of a given node:

Graph.java

package org.adrianwalker.continuedfractions.graph;

import java.math.BigDecimal;
import java.util.List;
import static org.adrianwalker.continuedfractions.Fraction.decimal;
import org.adrianwalker.continuedfractions.graph.controller.Controller;
import org.adrianwalker.continuedfractions.graph.entity.Node;
import org.adrianwalker.continuedfractions.graph.entity.NodePath;
import static org.adrianwalker.continuedfractions.graph.Path.sibling;
import static org.adrianwalker.continuedfractions.Fraction.fraction;

public final class Graph {

  private final Controller controller;

  public Graph(final Controller controller) {

    this.controller = controller;
  }

  public Node addNode(final String name) throws Exception {

    Node node = new Node();
    node.setName(name);

    return controller.create(node);
  }

  public NodePath addPath(final Node node, final int... path) throws Exception {

    NodePath nodePath = new NodePath();
    int[] nvDv = fraction(path);
    BigDecimal id = decimal(nvDv);
    int[] snvSdv = fraction(sibling(path));
    BigDecimal sid = decimal(snvSdv);

    nodePath.setId(id);
    nodePath.setSid(sid);
    nodePath.setHops(path.length);
    nodePath.setNode(node);

    return controller.create(nodePath);
  }

  public List<Node> tree(final Node node) {

    return controller.tree(node);
  }

  public List<Node> parents(final Node node) {

    return controller.parents(node);
  }

  public List<Node> children(final Node node) {

    return controller.children(node);
  }
}

Below is a unit test example of how to use the Graph class to create the graph in the image above:

GraphTest.java

package org.adrianwalker.continuedfractions.graph;

import java.util.Arrays;
import static java.util.stream.Collectors.toList;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import org.adrianwalker.continuedfractions.graph.controller.Controller;
import org.adrianwalker.continuedfractions.graph.entity.Node;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

public class GraphTest {

  private static EntityManagerFactory emf;

  public GraphTest() {
  }

  @BeforeClass
  public static void setUpClass() {
    emf = Persistence.createEntityManagerFactory("graph");
  }

  @AfterClass
  public static void tearDownClass() {
    emf.close();
  }

  @Before
  public void setUp() {
  }

  @After
  public void tearDown() {
  }

  /*
      A
     /|\
    B | C
     \|/
      D
      |
      E
   */
  @Test
  public void testGraph() throws Exception {

    Controller nc = new Controller(emf);
    Graph hierarchy = new Graph(nc);

    Node a = hierarchy.addNode("A");
    Node b = hierarchy.addNode("B");
    Node c = hierarchy.addNode("C");
    Node d = hierarchy.addNode("D");
    Node e = hierarchy.addNode("E");

    hierarchy.addPath(a, 1);
    hierarchy.addPath(b, 1, 1);
    hierarchy.addPath(c, 1, 2);
    hierarchy.addPath(d, 1, 3);
    hierarchy.addPath(d, 1, 1, 1);
    hierarchy.addPath(d, 1, 2, 1);
    hierarchy.addPath(e, 1, 1, 1, 1);
    hierarchy.addPath(e, 1, 2, 1, 1);
    hierarchy.addPath(e, 1, 3, 1);

    // trees
    Assert.assertEquals(Arrays.asList(new String[]{"A", "B", "C", "D", "E"}),
        hierarchy.tree(a).stream()
        .map(node -> node.getName())
        .collect(toList()));

    Assert.assertEquals(Arrays.asList(new String[]{"B", "D", "E"}),
        hierarchy.tree(b).stream()
        .map(node -> node.getName())
        .collect(toList()));

    Assert.assertEquals(Arrays.asList(new String[]{"C", "D", "E"}),
        hierarchy.tree(c).stream()
        .map(node -> node.getName())
        .collect(toList()));

    Assert.assertEquals(Arrays.asList(new String[]{"D", "E"}),
        hierarchy.tree(d).stream()
        .map(node -> node.getName())
        .collect(toList()));

    Assert.assertEquals(Arrays.asList(new String[]{"E"}),
        hierarchy.tree(e).stream()
        .map(node -> node.getName())
        .collect(toList()));

    // children
    Assert.assertEquals(Arrays.asList(new String[]{"B", "C", "D"}),
        hierarchy.children(a).stream()
        .map(node -> node.getName())
        .collect(toList()));

    Assert.assertEquals(Arrays.asList(new String[]{"D"}),
        hierarchy.children(b).stream()
        .map(node -> node.getName())
        .collect(toList()));

    Assert.assertEquals(Arrays.asList(new String[]{"D"}),
        hierarchy.children(c).stream()
        .map(node -> node.getName())
        .collect(toList()));

    Assert.assertEquals(Arrays.asList(new String[]{"E"}),
        hierarchy.children(d).stream()
        .map(node -> node.getName())
        .collect(toList()));

    Assert.assertEquals(Arrays.asList(new String[]{}),
        hierarchy.children(e).stream()
        .map(node -> node.getName())
        .collect(toList()));

    // parents
    Assert.assertEquals(Arrays.asList(new String[]{}),
        hierarchy.parents(a).stream()
        .map(node -> node.getName())
        .collect(toList()));

    Assert.assertEquals(Arrays.asList(new String[]{"A"}),
        hierarchy.parents(b).stream()
        .map(node -> node.getName())
        .collect(toList()));

    Assert.assertEquals(Arrays.asList(new String[]{"A"}),
        hierarchy.parents(c).stream()
        .map(node -> node.getName())
        .collect(toList()));

    Assert.assertEquals(Arrays.asList(new String[]{"A", "B", "C"}),
        hierarchy.parents(d).stream()
        .map(node -> node.getName())
        .collect(toList()));

    Assert.assertEquals(Arrays.asList(new String[]{"D"}),
        hierarchy.parents(e).stream()
        .map(node -> node.getName())
        .collect(toList()));
  }
}

Source Code

ANTLR Dynamic Runtime Tokens and Rules

ANTLR lexer tokens and parser rules are normally coded into the grammar and not modifiable during the codes execution, but I need to add lexer rule tokens and enable or disable parser rules at runtime. So here's an example of how you might want to do that.

First, two classes to hold lexer tokens and the enabled/disabled status of parser rules:

LexerLookup.java

package org.adrianwalker.antlr.dynamicrules;

import static java.lang.String.format;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.logging.Logger;
import static org.adrianwalker.antlr.dynamicrules.DynamicRulesParser.ruleNames;
import org.antlr.v4.runtime.CharStream;

public enum LexerLookup {

  INSTANCE;

  private static final Logger LOGGER = Logger.getLogger(LexerLookup.class.getName());
  private static final Comparator<String> LONGEST_FIRST = (s1, s2) -> s2.length() - s1.length();

  private final Map<Integer, Set<String>> tokenIdTermsMap;

  private LexerLookup() {
    tokenIdTermsMap = new HashMap<>();
  }

  public void put(final int tokenId, final List<String> tokens) {

    if (null == tokens) {
      throw new IllegalArgumentException("Illegal argument, tokens must be not null");
    }

    tokens.removeIf(Objects::isNull);

    Collections.sort(tokens, LONGEST_FIRST);

    LinkedHashSet tokenSet = new LinkedHashSet(tokens);

    LOGGER.info(format("tokens '%s' %s\n", ruleNames[tokenId - 1], tokenSet));

    this.tokenIdTermsMap.put(tokenId, tokenSet);
  }

  public boolean contains(final int tokenId, final CharStream input) {

    boolean contains = false;

    if (!tokenIdTermsMap.containsKey(tokenId)) {
      return contains;
    }

    Set<String> terms = tokenIdTermsMap.get(tokenId);

    for (String term : terms) {

      contains = ahead(term, input);

      if (contains) {
        LOGGER.info(format("contains '%s' ('%s')\n", term, ruleNames[tokenId - 1]));
        break;
      }
    }

    return contains;
  }

  private boolean ahead(final String word, final CharStream input) {

    for (int i = 0; i < word.length(); i++) {

      char wordChar = word.charAt(i);
      int inputChar = input.LA(i + 1);

      if (inputChar != wordChar) {
        return false;
      }
    }

    input.seek(input.index() + word.length() - 1);

    return true;
  }
}

ParserLookup.java

package org.adrianwalker.antlr.dynamicrules;

import static java.lang.String.format;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

public enum ParserLookup {

  INSTANCE;

  private static final Logger LOGGER = Logger.getLogger(ParserLookup.class.getName());
  private final Map<Integer, Boolean> ruleIdEnabledMap;

  private ParserLookup() {
    ruleIdEnabledMap = new HashMap<>();
  }

  public void put(final int ruleId, final boolean enabled) {

    LOGGER.info(format("ruleId = %s, enabled = %s\n", ruleId, enabled));

    this.ruleIdEnabledMap.put(ruleId, enabled);
  }

  public boolean enabled(final int ruleId) {

    return ruleIdEnabledMap.getOrDefault(ruleId, true);
  }
}
These two classes are used by the grammar to assign values to lexer rules and to enable or disable parser rules like this:

DynamicRules.g4

grammar DynamicRules;

@lexer::header {
  import org.adrianwalker.antlr.dynamicrules.LexerLookup;
}

@lexer::members {
  public static final LexerLookup LOOKUP = LexerLookup.INSTANCE;
}

@parser::header {
  import org.adrianwalker.antlr.dynamicrules.ParserLookup;
}

@parser::members {
  public static final ParserLookup LOOKUP = ParserLookup.INSTANCE;
}

// Parser Rules

sentence : ({LOOKUP.enabled(RULE_words)}? words) FULL_STOP ;
words : WORD (WS WORD)+ ;

// Lexer Rules

WORD : {LOOKUP.contains(WORD, _input)}? . ;
FULL_STOP : '.' ;
WS : [ \t\r\n]+ ;
OTHER : . ;

The parser and lexer generated from the ANLTR grammer can be used with the lexer and parser lookup classes to set token values and enable and disable rules:

SentenceParser.java

package org.adrianwalker.antlr.dynamicrules;

import java.util.List;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.RecognitionException;

public final class SentenceParser {

  public SentenceParser() {
  }

  public void setWords(final List<String> words) {
    LexerLookup.INSTANCE.put(DynamicRulesLexer.WORD, words);
  }

  public void enableWords(final boolean enabled) {
    ParserLookup.INSTANCE.put(DynamicRulesParser.RULE_words, enabled);
  }

  public Result parse(final String term) throws RecognitionException {

    DynamicRulesLexer lexer = new DynamicRulesLexer(new ANTLRInputStream(term));
    CommonTokenStream tokens = new CommonTokenStream(lexer);
    DynamicRulesParser parser = new DynamicRulesParser(tokens);

    return new Result(parser.sentence().getText(), parser.getNumberOfSyntaxErrors());
  }

  public static final class Result {

    private String text;
    private int numberOfSyntaxErrors;

    public Result(final String text, final int numberOfSyntaxErrors) {
      this.text = text;
      this.numberOfSyntaxErrors = numberOfSyntaxErrors;
    }

    public String getText() {
      return text;
    }

    public void setText(final String text) {
      this.text = text;
    }

    public int getNumberOfSyntaxErrors() {
      return numberOfSyntaxErrors;
    }

    public void setNumberOfSyntaxErrors(final int numberOfSyntaxErrors) {
      this.numberOfSyntaxErrors = numberOfSyntaxErrors;
    }
  }
}

Some unit tests for example usage:

SentenceParserTest.java

package org.adrianwalker.antlr.dynamicrules;

import static java.util.Arrays.asList;
import org.adrianwalker.antlr.dynamicrules.SentenceParser.Result;
import org.junit.Assert;
import org.junit.Test;

public class SentenceParserTest {

  @Test
  public void testValid() {

    SentenceParser parser = new SentenceParser();
    parser.enableWords(true);
    parser.setWords(asList(new String[]{
      "on", "cat", "mat", "sat", "the"
    }));

    Result result = parser.parse("the cat sat on the mat.");

    Assert.assertEquals("the cat sat on the mat.", result.getText());
    Assert.assertEquals(0, result.getNumberOfSyntaxErrors());
  }

  @Test
  public void testDisabledRule() {

    SentenceParser parser = new SentenceParser();
    parser.enableWords(false);
    parser.setWords(asList(new String[]{
      "on", "cat", "mat", "sat", "the"
    }));

    Result result = parser.parse("the cat sat on the mat.");

    Assert.assertEquals(1, result.getNumberOfSyntaxErrors());
  }

  @Test
  public void testInvalidWords() {

    SentenceParser parser = new SentenceParser();
    parser.enableWords(false);
    parser.setWords(asList(new String[]{
      "on", "cat", "mat", "sat", "the"
    }));

    Result result = parser.parse("INVALID");

    Assert.assertEquals(1, result.getNumberOfSyntaxErrors());
  }

  @Test
  public void testUpdateWordsAndDisableRule() {

    SentenceParser parser = new SentenceParser();
    parser.enableWords(true);
    parser.setWords(asList(new String[]{
      "the"
    }));

    Result result = parser.parse("the cat sat on the mat.");

    Assert.assertEquals(1, result.getNumberOfSyntaxErrors());

    parser.setWords(asList(new String[]{
      "on", "cat", "mat", "sat", "the"
    }));

    result = parser.parse("the cat sat on the mat.");

    Assert.assertEquals(0, result.getNumberOfSyntaxErrors());

    parser.enableWords(false);

    result = parser.parse("the cat sat on the mat.");

    Assert.assertEquals(1, result.getNumberOfSyntaxErrors());
  }
}

Source Code

Getting Started With AngularJS

I've only done a couple of projects with AngularJS, but the hardest part of using Angular each time has been just getting up and running. After that it's been pretty plain sailing. So here is a starting point to get me (and you?) hitting the ground running next time.

The project uses ngRoute and ngResource to create a single page app to call a REST web service (http://jsonplaceholder.typicode.com).

All the JavaScript dependencies are hosted on CloudFlair so there is nothing extra to download.

To turn this into a real project, you're going to ant to refactor the code a little, pull controllers and and services into their own files etc, but like I said, the code is just a starting point that works.

angularjs-demo.html

<!DOCTYPE html>
<html>
  <head>
    <title>Angular JS Demo</title>

    <link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/css/bootstrap.css" rel="stylesheet">

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.3/jquery.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/js/bootstrap.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.3/angular.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.3/angular-route.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular-resource/1.5.3/angular-resource.js"></script>

    <script src="angularjs-demo.js"></script>
  </head>

  <body ng-app="angularjs-demo">
    <div class="collapse navbar-collapse" role="navigation">
      <ul class="nav navbar-nav">
        <li><a href="#/posts">Posts</a></li>
      </ul>
    </div>

    <div class="container">
      <div class="panel panel-default">
        <div class="panel-body">
          <div ng-view></div>
        </div>
      </div>
    </div>
  </body>
</html>

posts.html

<!DOCTYPE html>
<html>
  <head>
    <title>Angular JS Demo - Posts</title>
  </head>
  <body>
    <div>
      <h4>Get Post by ID</h4>
      Post ID: <input type="text" ng-model="id" />
      <button ng-click="query(id)" type="button">Get Post</button>
    </div>
    <div>
      <h4>Post JSON</h4>
      <pre>{{postsResponse| json}}</pre>
    </div>
  </body>
</html>

angularjs-demo.js

angular
  .module('angularjs-demo', [
    'ngRoute',
    'ngResource'
  ])
  .config(function ($routeProvider) {
    $routeProvider
      .when('/posts', {
        templateUrl: 'posts.html',
        controller: 'PostsCtrl'
      })
      .otherwise({
        redirectTo: '/posts'
      });
  })
  .factory('Posts', function ($resource) {

    var POSTS_URL = 'http://jsonplaceholder.typicode.com/posts/:id';

    return $resource(POSTS_URL, {},
      {get: {method: 'GET'}}
    );
  })
  .controller('PostsCtrl', function ($scope, $log, Posts) {

    function query(id) {
      return Posts.get({id: id},
      function (data) {
        return data;
      }, function (err) {
        $log.error(JSON.stringify(err));
      });
    }

    $scope.query = function (id) {
      $log.info("id = " + id);
      $scope.postsResponse = query(id);
    };
  });

Source Code