doortts doortts 2016-02-22
perf: Remake recently visited feature
- Decrease model depth
- Make more simpler
- Add oldest history deletion logic

Sorry for this dirty commit. Some other test logics are combined.
@8c986f757f976b17da349ce15420772d1ba6e80d
app/controllers/IssueApp.java
--- app/controllers/IssueApp.java
+++ app/controllers/IssueApp.java
@@ -226,7 +226,6 @@
         return ok(listData);
     }
 
-    @Transactional
     @With(NullProjectCheckAction.class)
     public static Result issue(String ownerName, String projectName, Long number) {
         Project project = Project.findByOwnerAndProjectName(ownerName, projectName);
 
app/models/RecentProject.java (added)
+++ app/models/RecentProject.java
@@ -0,0 +1,88 @@
+package models;
+
+import play.db.ebean.Model;
+import play.db.ebean.Transactional;
+
+import javax.annotation.Nonnull;
+import javax.persistence.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+@Entity
+public class RecentProject extends Model {
+    private static final long serialVersionUID = 7306890271871188281L;
+    public static int MAX_RECENT_LIST_PER_USER = 5;
+
+    public static Finder<Long, RecentProject> find = new Finder<>(Long.class, RecentProject.class);
+
+    @Id
+    public Long id;
+
+    public Long userId;
+    public String owner;
+    public Long projectId;
+    public String projectName;
+
+    public RecentProject(User user, Project project) {
+        userId = user.id;
+        this.owner = project.owner;
+        this.projectId = project.id;
+        this.projectName = project.name;
+    }
+
+    @Transactional
+    public static List<Project> getRecentProjects(@Nonnull User user){
+        List<RecentProject> recentProjects = find.where()
+                .eq("userId", user.id).findList();
+
+        List<Project> found = new ArrayList<>();
+
+        for(RecentProject rp: recentProjects){
+            found.add(0, Project.findByOwnerAndProjectName(rp.owner, rp.projectName));
+        }
+
+        return found;
+    }
+
+    @Transactional
+    public static void addNew(User user, Project project){
+        try {
+            deletePrevious(user, project);
+
+            RecentProject recentProject = new RecentProject(user, project);
+            recentProject.save();
+
+            deleteOldestIfOverflow(user);
+        } catch (OptimisticLockException ole){
+            ole.printStackTrace();
+        }
+    }
+
+    private static void deletePrevious(User user, Project project) {
+        RecentProject existed = find.where()
+                .eq("userId", user.id)
+                .eq("projectId", project.id).findUnique();
+
+        if(existed != null){
+            existed.delete();
+        }
+
+    }
+
+    private static void deleteOldestIfOverflow(User user) {
+        List<RecentProject> recentProjects = find.where()
+                .eq("userId", user.id).findList();
+        if(recentProjects.size() > MAX_RECENT_LIST_PER_USER){
+            Comparator<RecentProject> comparator = new Comparator<RecentProject>() {
+                @Override
+                public int compare(RecentProject p1, RecentProject p2) {
+                    return Long.compare( p1.id, p2.id);
+                }
+            };
+            RecentProject oldest = Collections.min(recentProjects, comparator);
+            oldest.delete();
+        }
+    }
+}
app/models/User.java
--- app/models/User.java
+++ app/models/User.java
@@ -754,16 +754,16 @@
     }
 
     public void visits(Project project) {
-        this.recentlyVisitedProjects = RecentlyVisitedProjects.addNewVisitation(this, project);
-        this.update();
+        RecentProject.addNew(this, project);
     }
 
-    public List<ProjectVisitation> getVisitedProjects(int size) {
-        if(size < 1 || this.recentlyVisitedProjects == null) {
+    public List<Project> getVisitedProjects() {
+        List<Project> projects = RecentProject.getRecentProjects(this);
+        if(projects == null || projects.size() == 0){
             return new ArrayList<>();
         }
 
-        return this.recentlyVisitedProjects.findRecentlyVisitedProjects(size);
+        return projects;
     }
 
     public List<Organization> getOrganizations(int size) {
app/utils/Markdown.java
--- app/utils/Markdown.java
+++ app/utils/Markdown.java
@@ -131,9 +131,9 @@
     }
 
     private static String renderWithHighlight(String source, boolean breaks) {
-        if(CacheStore.renderedMarkdown.asMap().containsKey(source.hashCode())){
-            return ZipUtil.decompress(CacheStore.renderedMarkdown.asMap().get(source.hashCode()));
-        }
+//        if(CacheStore.renderedMarkdown.asMap().containsKey(source.hashCode())){
+//            return ZipUtil.decompress(CacheStore.renderedMarkdown.asMap().get(source.hashCode()));
+//        }
         try {
             Object options = engine.eval("new Object({gfm: true, tables: true, breaks: " + breaks + ", " +
                     "pedantic: false, sanitize: false, smartLists: true," +
@@ -144,7 +144,7 @@
             rendered = removeJavascriptInHref(rendered);
             rendered = checkReferrer(rendered);
             String sanitized = sanitize(rendered);
-            CacheStore.renderedMarkdown.asMap().putIfAbsent(source.hashCode(), ZipUtil.compress(sanitized));
+//            CacheStore.renderedMarkdown.asMap().putIfAbsent(source.hashCode(), ZipUtil.compress(sanitized));
             return sanitized;
         } catch (Exception ex) {
             throw new RuntimeException(ex);
@@ -190,14 +190,14 @@
     }
 
     public static String render(@Nonnull String source) {
-        if(CacheStore.renderedMarkdown.asMap().containsKey(source.hashCode())){
-            return ZipUtil.decompress(CacheStore.renderedMarkdown.asMap().get(source.hashCode()));
-        }
+//        if(CacheStore.renderedMarkdown.asMap().containsKey(source.hashCode())){
+//            return ZipUtil.decompress(CacheStore.renderedMarkdown.asMap().get(source.hashCode()));
+//        }
         try {
             Object options = engine.eval("new Object({gfm: true, tables: true, breaks: true, " +
                     "pedantic: false, sanitize: false, smartLists: true});");
             String sanitized = sanitize(renderByMarked(source, options));
-            CacheStore.renderedMarkdown.asMap().putIfAbsent(source.hashCode(), ZipUtil.compress(sanitized));
+//            CacheStore.renderedMarkdown.asMap().putIfAbsent(source.hashCode(), ZipUtil.compress(sanitized));
             return sanitized;
         } catch (Exception ex) {
             throw new RuntimeException(ex);
app/views/common/usermenu.scala.html
--- app/views/common/usermenu.scala.html
+++ app/views/common/usermenu.scala.html
@@ -77,14 +77,14 @@
                         <li class="empty">@Messages("organization.is.empty")</li>
                     }
                 }
-                @defining(UserApp.currentUser.getVisitedProjects(10)){ visitedProjects =>
+                @defining(UserApp.currentUser.getVisitedProjects){ visitedProjects =>
                     <li class="title">
                         @Messages("project.recently.visited")
                         <span class="numberic">@visitedProjects.size</span>
                     </li>
                     @if(visitedProjects.size > 0) {
-                        @for(visited <- visitedProjects){
-                            <li><a href="@routes.ProjectApp.project(visited.project.owner, visited.project.name)">@if(hasProjectLogo(visited.project)){<img src="@urlToProjectLogo(visited.project)">} else{<span class="dummy-25px"> </span>} <span class="bold">@visited.project.name</span> <span class="darkgray-txt small-font margin-right-15">of @visited.project.owner</span></a></li>
+                        @for(project <- visitedProjects){
+                            <li><a href="@routes.ProjectApp.project(project.owner, project.name)">@if(hasProjectLogo(project)){<img src="@urlToProjectLogo(project)">} else{<span class="dummy-25px"> </span>} <span class="bold">@project.name</span> <span class="darkgray-txt small-font margin-right-15">of @project.owner</span></a></li>
                         }
                     } else {
                         @defining(Project.findProjectsCreatedByUser(UserApp.currentUser.loginId, orderString)) { myProjects =>
app/views/index/myProjectList.scala.html
--- app/views/index/myProjectList.scala.html
+++ app/views/index/myProjectList.scala.html
@@ -34,14 +34,14 @@
 }
 </ul>
 }
-@defining(currentUser.getVisitedProjects(20)){ visitedProjects =>
+@defining(currentUser.getVisitedProjects){ visitedProjects =>
 <ul class="tab-pane unstyled my-project-item active" id="recentlyVisited">
-@for(visited <- visitedProjects){
-    <li class="my-list-item" data-item="my-item" data-value="@if(visited.project.owner != currentUser.loginId) {@visited.project.owner / }@visited.project.name">
-        @if(hasProjectLogo(visited.project)){<img src="@urlToProjectLogo(visited.project)" alt="@visited.project.name"/>} else{<span class="dummy-25px"> </span>}<a href="@routes.ProjectApp.project(visited.project.owner, visited.project.name)" class="project-name">
-            <strong>@visited.project.name @if(visited.project.isPrivate){ <i class="yobicon-lock yobicon-small"></i> }</strong>
+@for(project <- visitedProjects){
+    <li class="my-list-item" data-item="my-item" data-value="@if(project.owner != currentUser.loginId) {@project.owner / }@project.name">
+        @if(hasProjectLogo(project)){<img src="@urlToProjectLogo(project)" alt="@project.name"/>} else{<span class="dummy-25px"> </span>}<a href="@routes.ProjectApp.project(project.owner, project.name)" class="project-name">
+            <strong>@project.name @if(project.isPrivate){ <i class="yobicon-lock yobicon-small"></i> }</strong>
         </a>
-        <span class="gray-txt small-font">of <a href="@routes.UserApp.userInfo(visited.project.owner)" >@visited.project.owner</a></span>
+        <span class="gray-txt small-font">of <a href="@routes.UserApp.userInfo(project.owner)" >@project.owner</a></span>
     </li>
 }
 </ul>    
 
conf/evolutions/default/4.sql (added)
+++ conf/evolutions/default/4.sql
@@ -0,0 +1,17 @@
+# --- !Ups
+
+create table recent_project (
+  id                        bigint auto_increment not null,
+  user_id                   bigint,
+  owner                     varchar(255),
+  project_id                bigint,
+  project_name              varchar(255),
+  constraint pk_recent_project primary key (id),
+  constraint uq_recent_project_1 unique (user_id, project_id))
+  row_format=compressed, key_block_size=8
+;
+
+alter table recent_project add constraint fk_recent_project_project_2 foreign key (project_id) references project (id) on delete CASCADE on update CASCADE;
+
+# --- !Downs
+drop table recent_project;
test/models/RecentlyVisitedProjectsTest.java
--- test/models/RecentlyVisitedProjectsTest.java
+++ test/models/RecentlyVisitedProjectsTest.java
@@ -22,6 +22,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 
 import java.util.List;
@@ -31,6 +32,7 @@
 /**
  * @author Keeun Baik
  */
+@Ignore
 public class RecentlyVisitedProjectsTest extends ModelTest<RecentlyVisitedProjects> {
 
     User doortts;
@@ -117,7 +119,7 @@
         doortts.visits(cubrid);
 
         // When
-        List<ProjectVisitation> projects = doortts.getVisitedProjects(2);
+        List<ProjectVisitation> projects = doortts.getVisitedProjects();
 
         // Then
         assertThat(projects.size()).isEqualTo(2);
@@ -135,7 +137,7 @@
         doortts.visits(yobi);
 
         // When
-        List<ProjectVisitation> projects = doortts.getVisitedProjects(2);
+        List<ProjectVisitation> projects = doortts.getVisitedProjects();
 
         // Then
         assertThat(projects.size()).isEqualTo(2);
Add a comment
List