BAEL-86:
- Split controller into interfaces and implementations. - Added ability to up/download files.
This commit is contained in:
parent
a1a8c29c63
commit
da0f93feb5
|
@ -0,0 +1,58 @@
|
|||
package com.baeldung.spring.dispatcher.servlet.models;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class Attachment {
|
||||
private String id;
|
||||
|
||||
private String name;
|
||||
|
||||
private String description;
|
||||
|
||||
public Attachment() {
|
||||
this.id = UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
public Attachment(String name, String description) {
|
||||
this();
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
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 getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Attachment that = (Attachment) o;
|
||||
return id.equals(that.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
}
|
|
@ -3,6 +3,8 @@ package com.baeldung.spring.dispatcher.servlet.models;
|
|||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class Task {
|
||||
private String description;
|
||||
|
@ -10,6 +12,8 @@ public class Task {
|
|||
@DateTimeFormat(pattern = "yyyy-MM-dd'T'hh:mm")
|
||||
private Date due;
|
||||
|
||||
private Set<Attachment> attachments = new HashSet<>();
|
||||
|
||||
public Task() {
|
||||
}
|
||||
|
||||
|
@ -37,4 +41,12 @@ public class Task {
|
|||
public void setDue(Date due) {
|
||||
this.due = due;
|
||||
}
|
||||
|
||||
public Set<Attachment> getAttachments() {
|
||||
return attachments;
|
||||
}
|
||||
|
||||
public void setAttachments(Set<Attachment> attachments) {
|
||||
this.attachments = attachments;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package com.baeldung.spring.dispatcher.servlet.web;
|
||||
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
@RequestMapping("/attachments")
|
||||
public interface AttachmentController {
|
||||
@GetMapping("/{attachmentId}")
|
||||
ResponseEntity<FileSystemResource> getAttachment(
|
||||
@PathVariable("attachmentId") String attachmentId,
|
||||
@RequestParam(name = "download", required = false, defaultValue = "false") boolean forcedDownload
|
||||
);
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package com.baeldung.spring.dispatcher.servlet.web;
|
||||
|
||||
import com.baeldung.spring.dispatcher.servlet.models.Task;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import java.net.URLConnection;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Controller
|
||||
public class AttachmentControllerImpl implements AttachmentController {
|
||||
@Autowired
|
||||
private Map<String, List<Task>> taskMap;
|
||||
|
||||
@Override
|
||||
public ResponseEntity<FileSystemResource> getAttachment(
|
||||
@PathVariable("attachmentId") String attachmentId,
|
||||
@RequestParam(name = "download", required = false, defaultValue = "false") boolean forcedDownload
|
||||
) {
|
||||
FileSystemResource resource = new FileSystemResource("/tmp/" + attachmentId);
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
taskMap.values().stream()
|
||||
.flatMap(Collection::stream)
|
||||
.flatMap(t -> t.getAttachments().stream())
|
||||
.filter(a -> a.getId().equals(attachmentId))
|
||||
.findFirst()
|
||||
.ifPresent(a -> {
|
||||
headers.add("Content-Disposition",
|
||||
"attachment; filename=" + a.getName());
|
||||
headers.add("Content-Type", forcedDownload ?
|
||||
MediaType.APPLICATION_OCTET_STREAM_VALUE :
|
||||
URLConnection.guessContentTypeFromName(a.getName()));
|
||||
});
|
||||
return new ResponseEntity<>(resource, headers, HttpStatus.OK);
|
||||
}
|
||||
}
|
|
@ -1,28 +1,13 @@
|
|||
package com.baeldung.spring.dispatcher.servlet.web;
|
||||
|
||||
import com.baeldung.spring.dispatcher.servlet.models.Task;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/")
|
||||
public class HomeController {
|
||||
@Autowired
|
||||
private Map<String, List<Task>> taskMap;
|
||||
|
||||
public interface HomeController {
|
||||
@GetMapping("/*")
|
||||
public String home(Model model) {
|
||||
List<String> users = taskMap.keySet().stream()
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
model.addAttribute("users", users);
|
||||
return "home";
|
||||
}
|
||||
String home(
|
||||
Model model
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package com.baeldung.spring.dispatcher.servlet.web;
|
||||
|
||||
import com.baeldung.spring.dispatcher.servlet.models.Task;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Controller
|
||||
public class HomeControllerImpl implements HomeController {
|
||||
@Autowired
|
||||
private Map<String, List<Task>> taskMap;
|
||||
|
||||
@Override
|
||||
public String home(Model model) {
|
||||
List<String> users = taskMap.keySet().stream()
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
model.addAttribute("users", users);
|
||||
return "home";
|
||||
}
|
||||
}
|
|
@ -1,54 +1,49 @@
|
|||
package com.baeldung.spring.dispatcher.servlet.web;
|
||||
|
||||
import com.baeldung.spring.dispatcher.servlet.models.Task;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/tasks")
|
||||
public class TaskController {
|
||||
@Autowired
|
||||
private Map<String, List<Task>> taskMap;
|
||||
|
||||
public interface TaskController {
|
||||
@GetMapping("/{username}/list")
|
||||
public String listForm(
|
||||
Model model,
|
||||
@PathVariable("username") String username
|
||||
) {
|
||||
List<Task> tasks = taskMap.get(username).stream()
|
||||
.sorted(Comparator.comparing(Task::getDue))
|
||||
.collect(Collectors.toList());
|
||||
model.addAttribute("username", username);
|
||||
model.addAttribute("tasks", tasks);
|
||||
return "task-list";
|
||||
}
|
||||
String listTasks(
|
||||
@PathVariable("username") String username,
|
||||
Model model
|
||||
);
|
||||
|
||||
@GetMapping("/{username}/add")
|
||||
public String addForm(
|
||||
Model model,
|
||||
@PathVariable("username") String username
|
||||
) {
|
||||
model.addAttribute("username", username);
|
||||
model.addAttribute("task", new Task(new Date()));
|
||||
return "task-add";
|
||||
}
|
||||
String addTask(
|
||||
@PathVariable("username") String username,
|
||||
Model model
|
||||
);
|
||||
|
||||
@PostMapping("/{username}/add")
|
||||
public String addSubmit(
|
||||
String addTask(
|
||||
@PathVariable("username") String username,
|
||||
@ModelAttribute Task task
|
||||
) {
|
||||
List<Task> taskList = taskMap.get(username);
|
||||
if (taskList == null) {
|
||||
taskList = new ArrayList<>();
|
||||
}
|
||||
taskList.add(task);
|
||||
taskMap.put(username, taskList);
|
||||
return "redirect:list";
|
||||
}
|
||||
);
|
||||
|
||||
@GetMapping("/{username}/get/{id}")
|
||||
String getTask(
|
||||
@PathVariable("username") String username,
|
||||
@PathVariable("id") int id,
|
||||
Model model
|
||||
);
|
||||
|
||||
@GetMapping("/{username}/get/{id}/attach")
|
||||
String attachToTask(
|
||||
@PathVariable("username") String username,
|
||||
@PathVariable("id") int id,
|
||||
Model model
|
||||
);
|
||||
|
||||
@PostMapping("/{username}/get/{id}/attach")
|
||||
String attachToTask(
|
||||
@PathVariable("username") String username,
|
||||
@PathVariable("id") int id,
|
||||
@RequestParam("attachment") MultipartFile attachment,
|
||||
@RequestParam("description") String description
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
package com.baeldung.spring.dispatcher.servlet.web;
|
||||
|
||||
import com.baeldung.spring.dispatcher.servlet.models.Attachment;
|
||||
import com.baeldung.spring.dispatcher.servlet.models.Task;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Controller
|
||||
public class TaskControllerImpl implements TaskController {
|
||||
@Autowired
|
||||
private Map<String, List<Task>> taskMap;
|
||||
|
||||
@Override
|
||||
public String listTasks(
|
||||
@PathVariable("username") String username,
|
||||
Model model
|
||||
) {
|
||||
List<Task> tasks = taskMap.get(username).stream()
|
||||
.sorted(Comparator.comparing(Task::getDue))
|
||||
.collect(Collectors.toList());
|
||||
model.addAttribute("username", username);
|
||||
model.addAttribute("tasks", tasks);
|
||||
return "task-list";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String addTask(
|
||||
@PathVariable("username") String username,
|
||||
Model model
|
||||
) {
|
||||
model.addAttribute("username", username);
|
||||
model.addAttribute("task", new Task(new Date()));
|
||||
return "task-add";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String addTask(
|
||||
@PathVariable("username") String username,
|
||||
@ModelAttribute Task task
|
||||
) {
|
||||
List<Task> taskList = taskMap.get(username);
|
||||
if (taskList == null) {
|
||||
taskList = new ArrayList<>();
|
||||
}
|
||||
taskList.add(task);
|
||||
taskMap.put(username, taskList);
|
||||
return "redirect:list";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTask(
|
||||
@PathVariable("username") String username,
|
||||
@PathVariable("id") int id,
|
||||
Model model
|
||||
) {
|
||||
Task task = taskMap.get(username).get(id);
|
||||
model.addAttribute("username", username);
|
||||
model.addAttribute("id", id);
|
||||
model.addAttribute("task", task);
|
||||
return "task-get";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String attachToTask(
|
||||
@PathVariable("username") String username,
|
||||
@PathVariable("id") int id,
|
||||
Model model
|
||||
) {
|
||||
model.addAttribute("username", username);
|
||||
model.addAttribute("id", id);
|
||||
return "task-attach";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String attachToTask(
|
||||
@PathVariable("username") String username,
|
||||
@PathVariable("id") int id,
|
||||
@RequestParam("attachment") MultipartFile multipartFile,
|
||||
@RequestParam("description") String description
|
||||
) {
|
||||
Task task = taskMap.get(username).get(id);
|
||||
Attachment attachment = new Attachment(multipartFile.getOriginalFilename(),
|
||||
description);
|
||||
task.getAttachments().add(attachment);
|
||||
try (InputStream inputStream =
|
||||
new BufferedInputStream(multipartFile.getInputStream());
|
||||
OutputStream outputStream =
|
||||
new BufferedOutputStream(Files.newOutputStream(
|
||||
Paths.get("/tmp", attachment.getId())))) {
|
||||
byte[] buf = new byte[1024 * 16];
|
||||
int len;
|
||||
while ((len = inputStream.read(buf)) != -1) {
|
||||
outputStream.write(buf, 0, len);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to upload file!", e);
|
||||
}
|
||||
return "redirect:./";
|
||||
}
|
||||
}
|
|
@ -4,7 +4,19 @@ task.add.due=due
|
|||
task.add.header=Adding a task to {0}''s list:
|
||||
task.add.submit=Submit
|
||||
task.add.title={0}: task add
|
||||
task.attach.attachment=attached file
|
||||
task.attach.description=description
|
||||
task.attach.header=task #{0}: attach file
|
||||
task.attach.submit=Submit
|
||||
task.attach.title=#{0}: Attach file
|
||||
task.get.attach=Attach file
|
||||
task.get.attachments=attachments:
|
||||
task.get.download=Download
|
||||
task.get.header=task #{0}:
|
||||
task.get.list=Back
|
||||
task.get.title={0}: task details
|
||||
task.list.add-new=Add new
|
||||
task.list.details=Details
|
||||
task.list.header={0}''s tasks:
|
||||
task.list.home=Home
|
||||
task.list.title={0}: task list
|
||||
|
|
|
@ -4,7 +4,19 @@ task.add.due=f\u00e4llig
|
|||
task.add.header=F\u00fcge eine Aufgabe zu {0}''s Liste hinzu:
|
||||
task.add.submit=Senden
|
||||
task.add.title={0}: Task hinzuf\u00fcgen
|
||||
task.attach.attachment=Angeh\u00e4ngte Datei
|
||||
task.attach.description=Beschreibung
|
||||
task.attach.header=Task #{0}: Datei anh\u00e4ngen
|
||||
task.attach.submit=Senden
|
||||
task.attach.title=#{0}: Datei anh\u00e4ngen
|
||||
task.get.attach=Datei anh\u00e4ngen
|
||||
task.get.attachments=Anh\u00e4nge:
|
||||
task.get.download=Download
|
||||
task.get.header=Task #{0}:
|
||||
task.get.list=Zur\u00fcck
|
||||
task.get.title={0}: Task Details
|
||||
task.list.add-new=Neuer Task
|
||||
task.list.details=Details
|
||||
task.list.header={0}''s Tasks:
|
||||
task.list.home=Startseite
|
||||
task.list.title={0}: Task Liste
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Error</title>
|
||||
<link rel="stylesheet" type="text/css" href="/public/css/base.css">
|
||||
<link rel="stylesheet" type="text/css" th:href="${#themes.code('stylesheet')}">
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title th:text="#{home.title}"></title>
|
||||
<link rel="stylesheet" type="text/css" href="/public/css/base.css">
|
||||
<link rel="stylesheet" type="text/css" th:href="${#themes.code('stylesheet')}">
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
@ -2,14 +2,15 @@
|
|||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title th:text="#{task.add.title(${username})}"></title>
|
||||
<link rel="stylesheet" type="text/css" href="/public/css/base.css">
|
||||
<link rel="stylesheet" type="text/css" th:href="${#themes.code('stylesheet')}">
|
||||
</head>
|
||||
<body>
|
||||
<h2 th:text="#{task.add.header(${username})}"></h2>
|
||||
<form method="post" action="#" th:action="@{/tasks/{username}/add(username=${username})}" th:object="${task}">
|
||||
<input type="text" th:field="*{description}" data-th-placeholder="#{task.add.description}"/>
|
||||
<input type="datetime-local" th:field="*{due}" data-th-placeholder="#{task.add.due}"/>
|
||||
<input type="submit" data-th-value="#{task.add.submit}"/>
|
||||
<input type="text" th:field="*{description}" th:placeholder="#{task.add.description}"/>
|
||||
<input type="datetime-local" th:field="*{due}" th:placeholder="#{task.add.due}"/>
|
||||
<input type="submit" th:value="#{task.add.submit}"/>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title th:text="#{task.attach.title(${id})}"></title>
|
||||
<link rel="stylesheet" type="text/css" href="/public/css/base.css">
|
||||
<link rel="stylesheet" type="text/css" th:href="${#themes.code('stylesheet')}">
|
||||
</head>
|
||||
<body>
|
||||
<h2 th:text="#{task.attach.header(${id})}"></h2>
|
||||
<form method="post" action="#" enctype="multipart/form-data"
|
||||
th:action="@{/tasks/{username}/get/{id}/attach(username=${username},id=${id})}">
|
||||
<input type="file" name="attachment" th:placeholder="#{task.attach.attachment}"/>
|
||||
<input type="text" name="description" th:placeholder="#{task.attach.description}"/>
|
||||
<input type="submit" th:value="#{task.attach.submit}"/>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,28 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title th:text="#{task.get.title(${task.description})}"></title>
|
||||
<link rel="stylesheet" type="text/css" href="/public/css/base.css">
|
||||
<link rel="stylesheet" type="text/css" th:href="${#themes.code('stylesheet')}">
|
||||
</head>
|
||||
<body>
|
||||
<h2 th:text="#{task.get.header(${id})}"></h2>
|
||||
<p th:text="${task.due}"></p>
|
||||
<p th:text="${task.description}"></p>
|
||||
<hr/>
|
||||
<p th:text="#{task.get.attachments}"></p>
|
||||
<dl th:each="attachment : ${task.attachments}">
|
||||
<dt><img src="#" th:src="@{/attachments/{id}(id=${attachment.id})}"></dt>
|
||||
<dt>
|
||||
<span th:text="${attachment.name}"></span>
|
||||
<a class="button" th:href="@{/attachments/{id}(id=${attachment.id},download=true)}"
|
||||
th:text="#{task.get.download}"></a>
|
||||
</dt>
|
||||
<dd th:text="${attachment.description}"></dd>
|
||||
</dl>
|
||||
<p>
|
||||
<a th:href="@{/tasks/{username}/list(username=${username})}" th:text="#{task.get.list}"></a>
|
||||
<a th:href="@{/tasks/{username}/get/{id}/attach(username=${username},id=${id})}" th:text="#{task.get.attach}"></a>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
|
@ -2,6 +2,7 @@
|
|||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title th:text="#{task.list.title(${username})}"></title>
|
||||
<link rel="stylesheet" type="text/css" href="/public/css/base.css">
|
||||
<link rel="stylesheet" type="text/css" th:href="${#themes.code('stylesheet')}">
|
||||
</head>
|
||||
<body>
|
||||
|
@ -10,6 +11,7 @@
|
|||
<li th:each="task : ${tasks}">
|
||||
<p th:text="*{task.due}"></p>
|
||||
<p th:text="*{task.description}"></p>
|
||||
<a th:href="@{/tasks/{username}/get/{id}(username=${username},id=${taskStat.index})}" th:text="#{task.list.details}"></a>
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
a.button {
|
||||
-webkit-appearance: button;
|
||||
-moz-appearance: button;
|
||||
appearance: button;
|
||||
text-decoration: none;
|
||||
color: #2196f3;
|
||||
background-color: #e0e0e0;
|
||||
}
|
Loading…
Reference in New Issue