Projekt

Obecné

Profil

« Předchozí | Další » 

Revize 01f3ae23

Přidáno uživatelem Pavel Fidransky před více než 4 roky(ů)

re #41 global exception handler

Zobrazit rozdíly:

server/src/main/java/org/danekja/ymanager/business/ApiManager.java
11 11
import org.danekja.ymanager.repository.SettingsRepository;
12 12
import org.danekja.ymanager.repository.UserRepository;
13 13
import org.danekja.ymanager.repository.VacationRepository;
14
import org.danekja.ymanager.ws.rest.exceptions.RESTFullException;
15 14
import org.slf4j.Logger;
16 15
import org.slf4j.LoggerFactory;
17 16
import org.springframework.beans.factory.annotation.Autowired;
18
import org.springframework.dao.DataAccessException;
17
import org.springframework.http.HttpStatus;
19 18
import org.springframework.stereotype.Component;
19
import org.springframework.web.server.ResponseStatusException;
20 20

  
21 21
import javax.annotation.security.DenyAll;
22 22
import java.time.LocalDate;
......
49 49

  
50 50
    @Override
51 51
    @IsEmployer
52
    public List<VacationRequest> getVacationRequests(Status status) throws RESTFullException {
53
        try {
54
            return status == null ? requestRepository.getAllVacationRequests() : requestRepository.getAllVacationRequests(status);
55
        } catch (DataAccessException e) {
56
            log.error(e.getMessage());
57
            throw new RESTFullException(e.getMessage(), "database.error");
52
    public List<VacationRequest> getVacationRequests(Status status) {
53
        if (status == null) {
54
            return requestRepository.getAllVacationRequests();
55
        } else {
56
            return requestRepository.getAllVacationRequests(status);
58 57
        }
59 58
    }
60 59

  
61 60
    @Override
62 61
    @IsEmployer
63
    public List<AuthorizationRequest> getAuthorizationRequests(Status status) throws RESTFullException {
64
        try {
65
            return status == null ? requestRepository.getAllAuthorizations() : requestRepository.getAllAuthorizations(status);
66
        } catch (DataAccessException e) {
67
            log.error(e.getMessage());
68
            throw new RESTFullException(e.getMessage(), "database.error");
62
    public List<AuthorizationRequest> getAuthorizationRequests(Status status) {
63
        if (status == null) {
64
            return requestRepository.getAllAuthorizations();
65
        } else {
66
            return requestRepository.getAllAuthorizations(status);
69 67
        }
70 68
    }
71 69

  
72 70
    @Override
73 71
    @IsSignedIn
74
    public DefaultSettings getDefaultSettings() throws RESTFullException {
75
        try {
76
            return settingsRepository.get();
77
        } catch (DataAccessException e) {
78
            log.error(e.getMessage());
79
            throw new RESTFullException(e.getMessage(), "database.error");
80
        }
72
    public DefaultSettings getDefaultSettings() {
73
        return settingsRepository.get();
81 74
    }
82 75

  
83 76
    @Override
84 77
    @IsOwner
85
    public List<VacationDay> getUserCalendar(Long userId, LocalDate fromDate, LocalDate toDate, Status status) throws RESTFullException {
86
        try {
87
            List<VacationDay> vacations;
88
            if (status == null && toDate == null) {
89
                vacations = vacationRepository.getVacationDays(userId, fromDate);
90
            } else if (status == null) {
91
                vacations = vacationRepository.getVacationDays(userId, fromDate, toDate);
92
            } else if (toDate != null) {
93
                vacations = vacationRepository.getVacationDays(userId, fromDate, toDate, status);
94
            } else {
95
                vacations = vacationRepository.getVacationDays(userId, fromDate, status);
96
            }
97

  
98
            return vacations;
99

  
100
        } catch (DataAccessException e) {
101
            log.error(e.getMessage());
102
            throw new RESTFullException(e.getMessage(), "database.error");
78
    public List<VacationDay> getUserCalendar(long userId, LocalDate fromDate, LocalDate toDate, Status status) {
79
        List<VacationDay> vacations;
80
        if (status == null && toDate == null) {
81
            vacations = vacationRepository.getVacationDays(userId, fromDate);
82
        } else if (status == null) {
83
            vacations = vacationRepository.getVacationDays(userId, fromDate, toDate);
84
        } else if (toDate != null) {
85
            vacations = vacationRepository.getVacationDays(userId, fromDate, toDate, status);
86
        } else {
87
            vacations = vacationRepository.getVacationDays(userId, fromDate, status);
103 88
        }
89

  
90
        return vacations;
104 91
    }
105 92

  
106 93
    @Override
107 94
    @IsEmployer
108
    public void createSettings(DefaultSettings settings) throws RESTFullException {
109
        try {
110
            DefaultSettings defaultSettings = new DefaultSettings();
111
            defaultSettings.setSickDayCount(settings.getSickDayCount());
112
            defaultSettings.setNotification(settings.getNotification());
113
            userRepository.insertSettings(defaultSettings);
114
        } catch (DataAccessException e) {
115
            log.error(e.getMessage());
116
            throw new RESTFullException(e.getMessage(), "database.error");
117
        }
95
    public void createSettings(DefaultSettings settings) {
96
        DefaultSettings defaultSettings = new DefaultSettings();
97
        defaultSettings.setSickDayCount(settings.getSickDayCount());
98
        defaultSettings.setNotification(settings.getNotification());
99
        userRepository.insertSettings(defaultSettings);
118 100
    }
119 101

  
120 102
    @Override
121 103
    @IsOwner
122
    public void createVacation(Long userId, VacationDay vacationDay) throws RESTFullException {
123

  
104
    public void createVacation(long userId, VacationDay vacationDay) {
124 105
        if (vacationDay.getDate().isBefore(LocalDate.now())) {
125
            throw new RESTFullException("Vacation cannot be token in past.", "vacation.past.error");
106
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Vacation cannot be token in past.");
126 107
        }
127 108

  
128
        try {
129

  
130
            if (vacationRepository.isExistVacationForUser(userId, vacationDay.getDate())) {
131
                throw new RESTFullException("Cannot take a double vacation for the same day.", "vacation.double.error");
132
            }
133

  
134
            User user = userRepository.getUser(userId);
135
            vacationDay.setStatus(user.getRole() == UserRole.EMPLOYER ? Status.ACCEPTED : Status.PENDING);
136

  
137
            Vacation vacation = new Vacation();
138
            vacation.setDate(vacationDay.getDate());
139
            vacation.setFrom(vacationDay.getFrom());
140
            vacation.setTo(vacationDay.getTo());
141
            vacation.setStatus(vacationDay.getStatus());
142
            vacation.setType(vacationDay.getType());
109
        if (vacationRepository.isExistVacationForUser(userId, vacationDay.getDate())) {
110
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot take a double vacation for the same day.");
111
        }
143 112

  
144
            if (vacation.getType() == VacationType.VACATION) {
145
                user.takeVacation(vacation.getFrom(), vacation.getTo());
146
            } else {
147
                user.takeSickDay();
148
            }
113
        User user = userRepository.getUser(userId);
114
        vacationDay.setStatus(user.getRole() == UserRole.EMPLOYER ? Status.ACCEPTED : Status.PENDING);
149 115

  
150
            vacationRepository.insertVacationDay(userId, vacation);
151
            userRepository.updateUser(user);
116
        Vacation vacation = new Vacation();
117
        vacation.setDate(vacationDay.getDate());
118
        vacation.setFrom(vacationDay.getFrom());
119
        vacation.setTo(vacationDay.getTo());
120
        vacation.setStatus(vacationDay.getStatus());
121
        vacation.setType(vacationDay.getType());
152 122

  
153
        } catch (DataAccessException e) {
154
            log.error(e.getMessage());
155
            throw new RESTFullException(e.getMessage(), "database.error");
123
        if (vacation.getType() == VacationType.VACATION) {
124
            user.takeVacation(vacation.getFrom(), vacation.getTo());
125
        } else {
126
            user.takeSickDay();
156 127
        }
128

  
129
        vacationRepository.insertVacationDay(userId, vacation);
130
        userRepository.updateUser(user);
157 131
    }
158 132

  
159 133
    private void changeSettingsByEmployee(User user, UserSettings settings, DefaultSettings defaultSettings) {
......
198 172

  
199 173
    @Override
200 174
    @IsOwner
201
    public void changeSettings(Long userId, UserSettings settings) throws RESTFullException {
202

  
203
        try {
204
            UserRole invokedUserPermission = authService.getCurrentUser().getRole();
205
            boolean invokedUserIsAdmin = invokedUserPermission.equals(UserRole.EMPLOYER);
206
            DefaultSettings defaultSettings = getDefaultSettings();
207

  
208
            User userForChange = userRepository.getUser(settings.getId() == null ? userId : settings.getId());
175
    public void changeSettings(long userId, UserSettings settings) {
176
        UserRole invokedUserPermission = authService.getCurrentUser().getRole();
177
        boolean invokedUserIsAdmin = invokedUserPermission.equals(UserRole.EMPLOYER);
178
        DefaultSettings defaultSettings = getDefaultSettings();
209 179

  
210
            if (invokedUserIsAdmin) {
211
                changeSettingsByEmployer(userForChange, settings, defaultSettings);
212
            } else {
213
                changeSettingsByEmployee(userForChange, settings, defaultSettings);
214
            }
215

  
216
            userRepository.updateUserSettings(userForChange);
180
        User userForChange = userRepository.getUser(settings.getId() == null ? userId : settings.getId());
217 181

  
218
        } catch (DataAccessException e) {
219
            log.error(e.getMessage());
220
            throw new RESTFullException(e.getMessage(), "database.error");
182
        if (invokedUserIsAdmin) {
183
            changeSettingsByEmployer(userForChange, settings, defaultSettings);
184
        } else {
185
            changeSettingsByEmployee(userForChange, settings, defaultSettings);
221 186
        }
187

  
188
        userRepository.updateUserSettings(userForChange);
222 189
    }
223 190

  
224 191
    @Override
225 192
    //TODO not called anyway, allow after reimplementation
226 193
    @DenyAll
227
    public void changeVacation(VacationDay vacationDay) throws RESTFullException {
228
        try {
229
            Optional<Vacation> vacation = vacationRepository.getVacationDay(vacationDay.getId());
230
            if (vacation.isPresent()) {
231
                vacation.get().setDate(vacationDay.getDate());
232
                vacation.get().setStatus(vacationDay.getStatus());
233
                vacation.get().setType(vacationDay.getType());
234
                vacation.get().setTime(vacationDay.getFrom(), vacationDay.getTo());
235
                vacationRepository.updateVacationDay(vacation.get());
236
            }
237
        } catch (DataAccessException e) {
238
            log.error(e.getMessage());
239
            throw new RESTFullException(e.getMessage(), "database.error");
194
    public void changeVacation(VacationDay vacationDay) {
195
        Optional<Vacation> vacation = vacationRepository.getVacationDay(vacationDay.getId());
196
        if (vacation.isPresent()) {
197
            vacation.get().setDate(vacationDay.getDate());
198
            vacation.get().setStatus(vacationDay.getStatus());
199
            vacation.get().setType(vacationDay.getType());
200
            vacation.get().setTime(vacationDay.getFrom(), vacationDay.getTo());
201
            vacationRepository.updateVacationDay(vacation.get());
240 202
        }
241 203
    }
242 204

  
243 205
    @Override
244 206
    @IsEmployer
245
    public void changeRequest(RequestType type, BasicRequest request) throws RESTFullException {
246
        try {
247
            switch (type) {
248
                case VACATION: {
207
    public void changeRequest(RequestType type, BasicRequest request) {
208
        switch (type) {
209
            case VACATION:
210
                Optional<Vacation> vacationDayOpt = vacationRepository.getVacationDay(request.getId());
211
                if (!vacationDayOpt.isPresent()) {
212
                    throw new ResponseStatusException(HttpStatus.NOT_FOUND);
213
                }
249 214

  
250
                    Optional<Vacation> vacationDayOpt = vacationRepository.getVacationDay(request.getId());
215
                Vacation vacation = vacationDayOpt.get();
251 216

  
252
                    if (!vacationDayOpt.isPresent()) {
253
                        throw new RESTFullException("", "");
254
                    }
217
                if (request.getStatus().equals(Status.REJECTED)) {
218
                    User user = userRepository.getUser(vacation.getUserId());
255 219

  
256
                    Vacation vacation = vacationDayOpt.get();
257

  
258
                    if (request.getStatus().equals(Status.REJECTED)) {
259
                        User user = userRepository.getUser(vacation.getUserId());
260

  
261
                        switch (vacation.getType()) {
262
                            case VACATION: {
263
                                user.addVacationCount(vacation.getFrom(), vacation.getTo());
264
                                userRepository.updateUserTakenVacation(user);
265
                            } break;
266
                            case SICK_DAY: {
267
                                user.addTakenSickDayCount(-1);
268
                                userRepository.updateUserTakenSickDay(user);
269
                            } break;
270
                        }
220
                    switch (vacation.getType()) {
221
                        case VACATION: {
222
                            user.addVacationCount(vacation.getFrom(), vacation.getTo());
223
                            userRepository.updateUserTakenVacation(user);
224
                        } break;
225
                        case SICK_DAY: {
226
                            user.addTakenSickDayCount(-1);
227
                            userRepository.updateUserTakenSickDay(user);
228
                        } break;
271 229
                    }
230
                }
272 231

  
273
                    requestRepository.updateVacationRequest(vacation.getId(), request.getStatus());
232
                requestRepository.updateVacationRequest(vacation.getId(), request.getStatus());
233
                break;
274 234

  
275
                } break;
276
                case AUTHORIZATION: {
277
                    requestRepository.updateAuthorization(request);
278
                } break;
279
            }
280
        } catch (DataAccessException e) {
281
            log.error(e.getMessage());
282
            throw new RESTFullException(e.getMessage(), "database.error");
283
        } catch (IllegalArgumentException e) {
284
            throw new RESTFullException("Cannot create a domain object.", e.getMessage());
235
            case AUTHORIZATION:
236
                requestRepository.updateAuthorization(request);
237
                break;
285 238
        }
286 239
    }
287 240

  
288 241
    @Override
289 242
    @CanModifyVacation
290
    public void deleteVacation(Long vacationId) throws RESTFullException {
291
        try {
292
            Optional<Vacation> vacation = vacationRepository.getVacationDay(vacationId);
243
    public void deleteVacation(long vacationId) {
244
        Optional<Vacation> vacation = vacationRepository.getVacationDay(vacationId);
245
        if (!vacation.isPresent()) {
246
            throw new ResponseStatusException(HttpStatus.NOT_FOUND);
247
        }
293 248

  
294
            if (!vacation.isPresent()) {
295
                throw new RESTFullException("", "");
296
            }
249
        Vacation vacationDay = vacation.get();
297 250

  
298
            Vacation vacationDay = vacation.get();
299
            User user = userRepository.getUser(vacationDay.getUserId());
251
        if (!vacationDay.getDate().isAfter(LocalDate.now())) {
252
            return;
253
        }
300 254

  
301
            if (vacationDay.getDate().isAfter(LocalDate.now())) {
302
                if (!vacationDay.getStatus().equals(Status.REJECTED)) {
303
                    switch (vacationDay.getType()) {
304
                        case VACATION: {
305
                            user.addVacationCount(vacationDay.getFrom(), vacationDay.getTo());
306
                            userRepository.updateUserTakenVacation(user);
307
                        }
308
                        break;
309
                        case SICK_DAY: {
310
                            user.addTakenSickDayCount(-1);
311
                            userRepository.updateUserTakenSickDay(user);
312
                        }
313
                        break;
314
                    }
315
                }
316
                vacationRepository.deleteVacationDay(vacationDay.getId());
317
            }
318
        } catch (DataAccessException e) {
319
            log.error(e.getMessage());
320
            throw new RESTFullException(e.getMessage(), "database.error");
255
        if (vacationDay.getStatus().equals(Status.REJECTED)) {
256
            return;
257
        }
258

  
259
        User user = userRepository.getUser(vacationDay.getUserId());
260

  
261
        switch (vacationDay.getType()) {
262
            case VACATION:
263
                user.addVacationCount(vacationDay.getFrom(), vacationDay.getTo());
264
                userRepository.updateUserTakenVacation(user);
265
                break;
266

  
267
            case SICK_DAY:
268
                user.addTakenSickDayCount(-1);
269
                userRepository.updateUserTakenSickDay(user);
270
                break;
321 271
        }
272

  
273
        vacationRepository.deleteVacationDay(vacationDay.getId());
322 274
    }
323 275
}
server/src/main/java/org/danekja/ymanager/business/FileService.java
1 1
package org.danekja.ymanager.business;
2 2

  
3
import org.danekja.ymanager.ws.rest.exceptions.RESTFullException;
3
import java.io.IOException;
4 4

  
5 5
public interface FileService {
6 6

  
7
    FileImportResult parseXLSFile(String fileName, byte[] bytes) throws RESTFullException;
7
    FileImportResult parseXLSFile(String fileName, byte[] bytes) throws IOException;
8 8

  
9
    FileExportResult createPDFFile() throws RESTFullException;
9
    FileExportResult createPDFFile() throws IOException;
10 10

  
11 11
}
server/src/main/java/org/danekja/ymanager/business/FileServiceImpl.java
6 6
import org.apache.pdfbox.pdmodel.common.PDRectangle;
7 7
import org.apache.pdfbox.pdmodel.font.PDType1Font;
8 8
import org.danekja.ymanager.business.file.excel.*;
9
import org.danekja.ymanager.ws.rest.exceptions.RESTFullException;
10 9
import org.springframework.stereotype.Component;
11 10

  
12 11
import java.io.ByteArrayInputStream;
......
23 22
    }
24 23

  
25 24
    @Override
26
    public FileImportResult parseXLSFile(String fileName, byte[] bytes) throws RESTFullException {
25
    public FileImportResult parseXLSFile(String fileName, byte[] bytes) throws IOException {
27 26

  
28
        try {
29
            Map<String, Content> contentMap = getContentInfo(ExcelParser.parseXLSX(new ByteArrayInputStream(bytes)));
27
        Map<String, Content> contentMap = getContentInfo(ExcelParser.parseXLSX(new ByteArrayInputStream(bytes)));
30 28

  
31
            return new FileImportResult(fileName, (long) contentMap.size());
32

  
33
        } catch (IOException e) {
34
            throw new RESTFullException("", "");
35
        }
29
        return new FileImportResult(fileName, (long) contentMap.size());
36 30
    }
37 31

  
38 32
    private Map<String, Content> getContentInfo(Map<String, SheetContent[]> sheetForName) {
......
60 54

  
61 55

  
62 56
    @Override
63
    public FileExportResult createPDFFile() throws RESTFullException {
57
    public FileExportResult createPDFFile() throws IOException {
64 58

  
65 59
        PDDocument document = new PDDocument();
66 60
        PDPage page1 = new PDPage(PDRectangle.A4);
67 61
        document.addPage(page1);
68 62

  
69
        try {
70
            PDPageContentStream cos = new PDPageContentStream(document, page1);
63
        PDPageContentStream cos = new PDPageContentStream(document, page1);
71 64

  
72
            cos.beginText();
73
            cos.setFont(PDType1Font.HELVETICA, 12);
74
            cos.newLineAtOffset(100, 10);
75
            cos.showText("Test");
76
            cos.endText();
65
        cos.beginText();
66
        cos.setFont(PDType1Font.HELVETICA, 12);
67
        cos.newLineAtOffset(100, 10);
68
        cos.showText("Test");
69
        cos.endText();
77 70

  
78
            cos.close();
71
        cos.close();
79 72

  
80
            ByteArrayOutputStream output = new ByteArrayOutputStream();
73
        ByteArrayOutputStream output = new ByteArrayOutputStream();
81 74

  
82
            document.save(output);
83
            document.close();
75
        document.save(output);
76
        document.close();
84 77

  
85
            return new FileExportResult("export.pdf", output.toByteArray());
86

  
87
        } catch (IOException e) {
88
            throw new RESTFullException("", "");
89
        }
78
        return new FileExportResult("export.pdf", output.toByteArray());
90 79
    }
91 80
}
server/src/main/java/org/danekja/ymanager/business/Manager.java
4 4
import org.danekja.ymanager.domain.RequestType;
5 5
import org.danekja.ymanager.domain.Status;
6 6
import org.danekja.ymanager.dto.*;
7
import org.danekja.ymanager.ws.rest.exceptions.RESTFullException;
8 7

  
9 8
import java.time.LocalDate;
10 9
import java.util.List;
......
12 11
public interface Manager {
13 12

  
14 13

  
15
    List<VacationRequest> getVacationRequests(Status status) throws RESTFullException;
14
    List<VacationRequest> getVacationRequests(Status status);
16 15

  
17
    List<AuthorizationRequest> getAuthorizationRequests(Status status) throws RESTFullException;
16
    List<AuthorizationRequest> getAuthorizationRequests(Status status);
18 17

  
19
    DefaultSettings getDefaultSettings() throws RESTFullException;
18
    DefaultSettings getDefaultSettings();
20 19

  
21
    List<VacationDay> getUserCalendar(Long userId, LocalDate fromDate, LocalDate toDate, Status status) throws RESTFullException;
20
    List<VacationDay> getUserCalendar(long userId, LocalDate fromDate, LocalDate toDate, Status status);
22 21

  
23
    void createSettings(DefaultSettings settings) throws RESTFullException;
22
    void createSettings(DefaultSettings settings);
24 23

  
25
    void createVacation(Long userId, VacationDay vacationDay) throws RESTFullException;
24
    void createVacation(long userId, VacationDay vacationDay);
26 25

  
27
    void changeSettings(Long userId, UserSettings settings) throws RESTFullException;
26
    void changeSettings(long userId, UserSettings settings);
28 27

  
29
    void changeVacation(VacationDay vacationDay) throws RESTFullException;
28
    void changeVacation(VacationDay vacationDay);
30 29

  
31
    void changeRequest(RequestType type, BasicRequest request) throws RESTFullException;
30
    void changeRequest(RequestType type, BasicRequest request);
32 31

  
33
    void deleteVacation(Long vacationId) throws RESTFullException;
32
    void deleteVacation(long vacationId);
34 33
}
server/src/main/java/org/danekja/ymanager/business/UserManager.java
4 4
import org.danekja.ymanager.domain.Status;
5 5
import org.danekja.ymanager.domain.User;
6 6
import org.danekja.ymanager.dto.BasicProfileUser;
7
import org.danekja.ymanager.ws.rest.exceptions.RESTFullException;
8 7
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
9 8

  
10 9
import java.util.List;
......
21 20
     *
22 21
     * @param status status filter value
23 22
     * @return list of users or empty list if none found
24
     * @throws RESTFullException
25 23
     */
26
    List<BasicProfileUser> getUsers(Status status) throws RESTFullException;
24
    List<BasicProfileUser> getUsers(Status status);
27 25

  
28 26
    /**
29 27
     * Gets user by id (PK)
server/src/main/java/org/danekja/ymanager/business/impl/DefaultUserManager.java
8 8
import org.danekja.ymanager.repository.SettingsRepository;
9 9
import org.danekja.ymanager.repository.UserRepository;
10 10
import org.danekja.ymanager.repository.VacationRepository;
11
import org.danekja.ymanager.ws.rest.exceptions.RESTFullException;
12 11
import org.slf4j.Logger;
13 12
import org.slf4j.LoggerFactory;
14 13
import org.springframework.beans.factory.annotation.Autowired;
15
import org.springframework.dao.DataAccessException;
14
import org.springframework.dao.IncorrectResultSizeDataAccessException;
16 15
import org.springframework.security.core.userdetails.UserDetailsService;
17 16
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
18 17
import org.springframework.stereotype.Service;
......
51 50
    public GoogleUser getUser(OidcIdToken token) {
52 51
        //TODO replace with better user object creation after we update the data model - registered and oauth users should be
53 52
        //TODO in separate tables. We do this here because we need the ID value set
54
        User user = userRepository.getUser(token.getEmail());
55

  
56
        return user != null ? new GoogleUser(user.getId(), user.getUserData(), token) : null;
53
        try {
54
            User user = userRepository.getUser(token.getEmail());
55
            return new GoogleUser(user.getId(), user.getUserData(), token);
56
        } catch (IncorrectResultSizeDataAccessException e) {
57
            return null;
58
        }
57 59
    }
58 60

  
59 61
    @Override
60 62
    @IsEmployer
61
    public List<BasicProfileUser> getUsers(Status status) throws RESTFullException {
62
        try {
63
            List<BasicProfileUser> users = userRepository.getAllBasicUsers(status == null ? Status.ACCEPTED : status);
64

  
65
            LocalDate today = LocalDate.now();
66
            LocalDate weekBefore = today.minusWeeks(1);
67
            LocalDate weekAfter = today.plusWeeks(1);
68
            for (BasicProfileUser user : users) {
69
                user.setCalendar(vacationRepository.getVacationDays(user.getId(), weekBefore, weekAfter));
70
            }
71

  
72
            return users;
73

  
74
        } catch (DataAccessException e) {
75
            LOG.error(e.getMessage());
76
            throw new RESTFullException(e.getMessage(), "database.error");
63
    public List<BasicProfileUser> getUsers(Status status) {
64
        List<BasicProfileUser> users = userRepository.getAllBasicUsers(status == null ? Status.ACCEPTED : status);
65

  
66
        LocalDate today = LocalDate.now();
67
        LocalDate weekBefore = today.minusWeeks(1);
68
        LocalDate weekAfter = today.plusWeeks(1);
69
        for (BasicProfileUser user : users) {
70
            user.setCalendar(vacationRepository.getVacationDays(user.getId(), weekBefore, weekAfter));
77 71
        }
72

  
73
        return users;
78 74
    }
79 75

  
80 76
    @Override
server/src/main/java/org/danekja/ymanager/repository/RepositoryUtils.java
1
package org.danekja.ymanager.repository;
2

  
3
import org.springframework.dao.IncorrectResultSizeDataAccessException;
4

  
5
import java.util.List;
6

  
7
public class RepositoryUtils {
8

  
9
    /**
10
     * Method which extracts single result from a list which is expected to contains 0 or 1 results.
11
     *
12
     * @param queryResult query result in form of {@link List}
13
     * @param <T>         type of object expected in the list
14
     * @return the object from the list or null (when empty)
15
     * @throws IncorrectResultSizeDataAccessException when #queryResult contains more than 1 object
16
     */
17
    public static <T> T singleResult(List<T> queryResult) {
18
        switch (queryResult.size()) {
19
            case 0:
20
                return null;
21
            case 1:
22
                return queryResult.get(0);
23
            default:
24
                throw new IncorrectResultSizeDataAccessException(1, queryResult.size());
25
        }
26
    }
27
}
server/src/main/java/org/danekja/ymanager/repository/UserRepository.java
186 186
     * @return found user object or null (if not found)
187 187
     */
188 188
    public RegisteredUser getUser(final String email) {
189
        List<RegisteredUser> users = this.jdbc.query("SELECT * FROM end_user WHERE email = ?", USER_MAPPER, email);
190

  
191
        return RepositoryUtils.singleResult(users);
189
        return this.jdbc.queryForObject("SELECT * FROM end_user WHERE email = ?", USER_MAPPER, email);
192 190
    }
193 191

  
194 192
    /**
......
198 196
     * @return found user object or null (if not found)
199 197
     */
200 198
    public RegisteredUser getUser(final long id) {
201
        List<RegisteredUser> users = this.jdbc.query("SELECT * FROM end_user WHERE id = ?", USER_MAPPER, id);
202

  
203
        return RepositoryUtils.singleResult(users);
199
        return this.jdbc.queryForObject("SELECT * FROM end_user WHERE id = ?", USER_MAPPER, id);
204 200
    }
205 201

  
206 202
    /**
......
210 206
     * @return
211 207
     */
212 208
    public UserData getUserData(final long id) {
213
        List<UserData> users = this.jdbc.query("SELECT * FROM end_user WHERE id = ?", USER_DATA_MAPPER, id);
214

  
215
        return RepositoryUtils.singleResult(users);
209
        return this.jdbc.queryForObject("SELECT * FROM end_user WHERE id = ?", USER_DATA_MAPPER, id);
216 210
    }
217 211

  
218 212
    public void updateUser(final User user) {
server/src/main/java/org/danekja/ymanager/ws/rest/ApiController.java
1 1
package org.danekja.ymanager.ws.rest;
2 2

  
3
import org.danekja.ymanager.business.FileExportResult;
3 4
import org.danekja.ymanager.business.FileService;
4 5
import org.danekja.ymanager.business.Manager;
5 6
import org.danekja.ymanager.domain.RequestType;
6 7
import org.danekja.ymanager.domain.Status;
7 8
import org.danekja.ymanager.domain.User;
8
import org.danekja.ymanager.dto.BasicRequest;
9
import org.danekja.ymanager.dto.DefaultSettings;
10
import org.danekja.ymanager.dto.UserSettings;
11
import org.danekja.ymanager.dto.VacationDay;
12
import org.danekja.ymanager.util.localization.Language;
9
import org.danekja.ymanager.dto.*;
13 10
import org.springframework.beans.factory.annotation.Autowired;
14 11
import org.springframework.http.HttpHeaders;
15 12
import org.springframework.http.ResponseEntity;
......
19 16

  
20 17
import java.time.LocalDate;
21 18
import java.time.format.DateTimeFormatter;
19
import java.util.List;
22 20

  
23 21
import static org.springframework.web.bind.annotation.RequestMethod.*;
24 22

  
25 23
@RestController
26
public class ApiController extends BaseController {
24
public class ApiController {
27 25

  
28 26
    private final Manager manager;
29 27

  
......
38 36
    // *********************** GET ****************************
39 37

  
40 38
    @RequestMapping(value = "/users/requests/vacation", method=GET)
41
    public ResponseEntity usersRequestsVacation(
39
    public List<VacationRequest> usersRequestsVacation(
42 40
            @RequestParam(value = "lang", required = false) String lang,
43 41
            @RequestParam(value = "status", required = false) String status)
44 42
    {
45
        return handle(Language.getLanguage(lang), () ->
46
                manager.getVacationRequests(Status.getStatus(status))
47
        );
43
        return manager.getVacationRequests(Status.getStatus(status));
48 44
    }
49 45

  
50 46
    @RequestMapping(value = "/users/requests/authorization", method=GET)
51
    public ResponseEntity userRequestsAuthorization(
47
    public List<AuthorizationRequest> userRequestsAuthorization(
52 48
            @RequestParam(value = "lang", required = false) String lang,
53 49
            @RequestParam(value = "status", required = false) String status)
54 50
    {
55
        return handle(Language.getLanguage(lang), () ->
56
                manager.getAuthorizationRequests(Status.getStatus(status))
57
         );
51
        return manager.getAuthorizationRequests(Status.getStatus(status));
58 52
    }
59 53

  
60 54
    @RequestMapping(value = "/user/{id}/calendar", method=GET)
61
    public ResponseEntity userCalendar(
55
    public List<VacationDay> userCalendar(
62 56
            @PathVariable("id") Long id,
63 57
            @RequestParam(value = "lang", required = false) String lang,
64 58
            @RequestParam(value = "from") String from,
65 59
            @RequestParam(value = "to", required = false) String to,
66 60
            @RequestParam(value = "status", required = false) String status)
67 61
    {
68
        return handle(Language.getLanguage(lang), () -> {
69
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
70
            LocalDate fromDate = LocalDate.parse(from, formatter);
71
            LocalDate toDate = to != null ? LocalDate.parse(to, formatter) : null;
72
            return manager.getUserCalendar(id, fromDate, toDate, Status.getStatus(status));
73
        });
62
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
63
        LocalDate fromDate = LocalDate.parse(from, formatter);
64
        LocalDate toDate = to != null ? LocalDate.parse(to, formatter) : null;
65
        return manager.getUserCalendar(id, fromDate, toDate, Status.getStatus(status));
74 66
    }
75 67

  
76 68
    @RequestMapping(value = "/settings", method=GET)
77
    public ResponseEntity settings(
69
    public DefaultSettings settings(
78 70
            @RequestParam(value = "lang", required = false) String lang)
79 71
    {
80
        return handle(Language.getLanguage(lang), () -> new DefaultSettings(manager.getDefaultSettings()));
72
        return new DefaultSettings(manager.getDefaultSettings());
81 73
    }
82 74

  
83 75
    // *********************** POST ****************************
84 76

  
85 77
    @RequestMapping(value = "/settings", method=POST)
86
    public ResponseEntity settings(
78
    public ResponseEntity<Void> settings(
87 79
            @RequestParam(value = "lang", required = false) String lang,
88 80
            @RequestBody DefaultSettings settings)
89 81
    {
90
        return handle(Language.getLanguage(lang), () ->
91
                manager.createSettings(settings.toEntity())
92
        );
82
        manager.createSettings(settings.toEntity());
83

  
84
        return ResponseEntity.ok().build();
93 85
    }
94 86

  
95 87
    @RequestMapping(value = "/user/calendar/create", method=POST)
96
    public ResponseEntity userCalendarCreate(
88
    public ResponseEntity<Void> userCalendarCreate(
97 89
            @RequestParam(value = "lang", required = false) String lang,
98 90
            @RequestBody VacationDay vacationDay,
99 91
            Authentication auth)
100 92
    {
101 93
        //TODO make api endpoint contain userId in path as part of #39, also drop the create part of path
102 94
        //TODO drop the auth parameter afterwards
103
        return handle(Language.getLanguage(lang), () ->
104
                manager.createVacation(((User) auth.getPrincipal()).getId(), vacationDay)
105
        );
95
        manager.createVacation(((User) auth.getPrincipal()).getId(), vacationDay);
96

  
97
        return ResponseEntity.ok().build();
106 98
    }
107 99

  
108 100
    // *********************** PUT ****************************
109 101

  
110 102

  
111 103
    @RequestMapping(value = "/user/settings", method=PUT)
112
    public ResponseEntity userSettings(
104
    public ResponseEntity<Void> userSettings(
113 105
            @RequestParam(value = "lang", required = false) String lang,
114 106
            @RequestBody UserSettings settings,
115 107
            Authentication auth)
116 108
    {
117 109
        //TODO make api endpoint contain userId in path as part of #39
118 110
        //TODO drop the auth parameter afterwards
119
        return handle(Language.getLanguage(lang), () ->
120
                manager.changeSettings(((User) auth.getPrincipal()).getId(), settings)
121
        );
111
        manager.changeSettings(((User) auth.getPrincipal()).getId(), settings);
112

  
113
        return ResponseEntity.ok().build();
122 114
    }
123 115

  
124 116
    @RequestMapping(value = "/user/calendar/edit", method=PUT)
125
    public ResponseEntity userCalendarEdit(
117
    public ResponseEntity<Void> userCalendarEdit(
126 118
            @RequestParam(value = "lang", required = false) String lang,
127 119
            @RequestBody VacationDay vacationDay)
128 120
    {
129 121
        //TODO make api endpoint point to vacation endpoint as part of #39, also drop the edit part of path
130 122
        //TODO drop the auth parameter afterwards
131
        return handle(Language.getLanguage(lang), () ->
132
                manager.changeVacation(vacationDay)
133
        );
123
        manager.changeVacation(vacationDay);
124

  
125
        return ResponseEntity.ok().build();
134 126
    }
135 127

  
136 128
    @RequestMapping(value = "/user/requests", method=PUT)
137
    public ResponseEntity userRequests(
129
    public ResponseEntity<Void> userRequests(
138 130
            @RequestParam(value = "lang", required = false) String lang,
139 131
            @RequestParam(value = "type", required = true) String type,
140 132
            @RequestBody BasicRequest request)
141 133
    {
142
        return handle(Language.getLanguage(lang), () ->
143
                manager.changeRequest(RequestType.getType(type), request)
144
        );
134
        manager.changeRequest(RequestType.getType(type), request);
135

  
136
        return ResponseEntity.ok().build();
145 137
    }
146 138

  
147 139
    // *********************** DELETE ****************************
148 140

  
149 141
    @RequestMapping(value = "/calendar/{id}/delete", method=DELETE)
150
    public ResponseEntity calendarDelete(
142
    public ResponseEntity<Void> calendarDelete(
151 143
            @PathVariable("id") Long id,
152 144
            @RequestParam(value = "lang", required = false) String lang)
153 145
    {
154
        return handle(Language.getLanguage(lang), () ->
155
                manager.deleteVacation(id)
156
        );
146
        manager.deleteVacation(id);
147

  
148
        return ResponseEntity.ok().build();
157 149
    }
158 150

  
159 151
    // *********************** FILE ****************************
160 152

  
161 153
    @RequestMapping(value = "/import/xls", method=POST)
162
    public ResponseEntity importXLSFile(
163
            @RequestParam(value = "lang", required = false) String lang,
164
            @RequestParam("file") MultipartFile file)
165
    {
166
        return handle(Language.getLanguage(lang), () ->
167
            fileService.parseXLSFile(file.getOriginalFilename(), file.getBytes())
168
        );
154
    public ResponseEntity<Void> importXLSFile(@RequestParam(value = "lang", required = false) String lang, @RequestParam("file") MultipartFile file) throws Exception {
155
        fileService.parseXLSFile(file.getOriginalFilename(), file.getBytes());
156

  
157
        return ResponseEntity.ok().build();
169 158
    }
170 159

  
171 160
    @RequestMapping(value = "/export/pdf", method=GET)
172
    public ResponseEntity exportPDFFile(
173
            @RequestParam(value = "lang", required = false) String lang)
174
    {
175
        return handle(Language.getLanguage(lang),
176
                () -> fileService.createPDFFile(),
177
                (res) -> new String[]{HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + res.getName() + "\""},
178
                (res) -> res.getBytes()
179
        );
161
    public ResponseEntity<byte[]> exportPDFFile(@RequestParam(value = "lang", required = false) String lang) throws Exception {
162
        FileExportResult result = fileService.createPDFFile();
163

  
164
        return ResponseEntity.ok()
165
                .header(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment; filename=\"%s\"", result.getName()))
166
                .body(result.getBytes());
180 167
    }
181 168
}
server/src/main/java/org/danekja/ymanager/ws/rest/BaseController.java
1
package org.danekja.ymanager.ws.rest;
2

  
3
import org.danekja.ymanager.util.localization.Language;
4
import org.danekja.ymanager.util.localization.Message;
5
import org.danekja.ymanager.ws.rest.exceptions.RESTFullException;
6
import org.slf4j.Logger;
7
import org.slf4j.LoggerFactory;
8
import org.springframework.http.MediaType;
9
import org.springframework.http.ResponseEntity;
10

  
11
import java.util.Arrays;
12
import java.util.HashMap;
13
import java.util.Map;
14
import java.util.function.Function;
15

  
16
import static org.springframework.http.HttpStatus.OK;
17
import static org.springframework.http.ResponseEntity.ok;
18

  
19
public abstract class BaseController {
20

  
21
    protected final Logger LOG = LoggerFactory.getLogger(this.getClass());
22

  
23
    protected <T> ResponseEntity handle(Language language, RESTGetHandler<T> handler) {
24
        return handle(language, handler, null, null);
25
    }
26

  
27
    protected <T> ResponseEntity handle(Language language, RESTGetHandler<T> handler, Function<T, String[]> header, Function<T, Object> bodyValue) {
28
        try {
29
            T result = handler.get();
30

  
31
            ResponseEntity.BodyBuilder response = ResponseEntity.ok();
32

  
33
            if (header != null) {
34
                String[] headers = header.apply(result);
35

  
36
                if (headers.length > 1) {
37
                    response.header(headers[0], Arrays.copyOfRange(headers, 1, headers.length - 1));
38
                } else if (headers.length == 1) {
39
                    response.header(headers[0]);
40
                }
41
            }
42

  
43
            return response.body(bodyValue != null ? bodyValue.apply(result) : result);
44

  
45
        } catch (RESTFullException e) {
46
            LOG.error(e.getMessage());
47
            return sendError(400, e.getLocalizedMessage(), language);
48
        } catch (Exception e) {
49
            LOG.error(e.getMessage());
50
            return sendError(401, "rest.exception.generic", language);
51
        }
52
    }
53

  
54
    protected ResponseEntity sendError(Integer errorCode, String messageKey, Language language) {
55
        String localizedMessage = Message.getString(language, messageKey);
56
        Map<String, String> result = new HashMap<>();
57
        result.put("error", errorCode.toString());
58
        result.put("message", localizedMessage);
59
        return ResponseEntity.status(errorCode).contentType(MediaType.APPLICATION_JSON).body(result);
60
    }
61

  
62
    protected <T> ResponseEntity handle(Language language, RESTInvokeHandler<T> handler) {
63
        try {
64
            handler.invoke();
65
            return ok(OK);
66
        } catch (RESTFullException e) {
67
            LOG.error(e.getMessage());
68
            return sendError(400, e.getLocalizedMessage(), language);
69
        } catch (Exception e) {
70
            LOG.error(e.getMessage());
71
            return sendError(401, e.getMessage(), language);
72
        }
73
    }
74
}
server/src/main/java/org/danekja/ymanager/ws/rest/CustomExceptionHandler.java
1
package org.danekja.ymanager.ws.rest;
2

  
3
import org.slf4j.Logger;
4
import org.slf4j.LoggerFactory;
5
import org.springframework.dao.DataAccessException;
6
import org.springframework.dao.EmptyResultDataAccessException;
7
import org.springframework.http.HttpHeaders;
8
import org.springframework.http.HttpStatus;
9
import org.springframework.http.ResponseEntity;
10
import org.springframework.security.access.AccessDeniedException;
11
import org.springframework.web.bind.annotation.ControllerAdvice;
12
import org.springframework.web.bind.annotation.ExceptionHandler;
13
import org.springframework.web.bind.annotation.ResponseBody;
14
import org.springframework.web.context.request.WebRequest;
15
import org.springframework.web.server.ResponseStatusException;
16
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
17

  
18
import java.util.HashMap;
19
import java.util.Map;
20

  
21
@ControllerAdvice
22
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
23
    @ExceptionHandler(DataAccessException.class)
24
    @ResponseBody
25
    public final ResponseEntity<Object> handleDataAccessException(DataAccessException ex, WebRequest request) {
26
        Logger log = getExceptionLogger(ex);
27
        log.error(ex.getMessage());
28
        log.debug(ex.getMessage(), ex);
29

  
30
        Map<String, String> result = new HashMap<>();
31
        result.put("message", ex.getMessage());
32

  
33
        return super.handleExceptionInternal(ex, result, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR, request);
34
    }
35

  
36
    @ExceptionHandler(EmptyResultDataAccessException.class)
37
    @ResponseBody
38
    public final ResponseEntity<Object> handleEmptyResultDataAccessException(EmptyResultDataAccessException ex, WebRequest request) {
39
        Logger log = getExceptionLogger(ex);
40
        log.debug(ex.getMessage());
41
        log.debug(ex.getMessage(), ex);
42

  
43
        return super.handleExceptionInternal(ex, null, new HttpHeaders(), HttpStatus.NOT_FOUND, request);
44
    }
45

  
46
    @ExceptionHandler(IllegalArgumentException.class)
47
    @ResponseBody
48
    public final ResponseEntity<Object> handleIllegalArgumentException(IllegalArgumentException ex, WebRequest request) {
49
        Logger log = getExceptionLogger(ex);
50
        log.debug(ex.getMessage());
51
        log.debug(ex.getMessage(), ex);
52

  
53
        Map<String, String> result = new HashMap<>();
54
        result.put("message", ex.getMessage());
55

  
56
        return super.handleExceptionInternal(ex, result, new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
57
    }
58

  
59
    @ExceptionHandler(AccessDeniedException.class)
60
    @ResponseBody
61
    public final ResponseEntity<Object> handleAccessDeniedException(AccessDeniedException ex, WebRequest request) {
62
        Logger log = getExceptionLogger(ex);
63
        log.error(ex.getMessage());
64
        log.debug(ex.getMessage(), ex);
65

  
66
        Map<String, String> result = new HashMap<>();
67
        result.put("message", ex.getMessage());
68

  
69
        return super.handleExceptionInternal(ex, result, new HttpHeaders(), HttpStatus.FORBIDDEN, request);
70
    }
71

  
72
    @ExceptionHandler(ResponseStatusException.class)
73
    @ResponseBody
74
    public final ResponseEntity<Object> handleResponseStatusException(ResponseStatusException ex, WebRequest request) {
75
        Logger log = getExceptionLogger(ex);
76
        log.error(ex.getMessage());
77
        log.debug(ex.getMessage(), ex);
78

  
79
        Map<String, String> result = new HashMap<>();
80
        result.put("message", ex.getReason());
81

  
82
        return super.handleExceptionInternal(ex, result, new HttpHeaders(), ex.getStatus(), request);
83
    }
84

  
85
    @ExceptionHandler(Exception.class)
86
    @ResponseBody
87
    public final ResponseEntity<Object> handleAll(Exception ex, WebRequest request) {
88
        Logger log = getExceptionLogger(ex);
89
        log.error(ex.getMessage());
90
        log.debug(ex.getMessage(), ex);
91

  
92
        Map<String, String> result = new HashMap<>();
93
        result.put("message", ex.getMessage());
94

  
95
        return super.handleExceptionInternal(ex, result, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR, request);
96
    }
97

  
98
    protected Logger getExceptionLogger(Exception ex) {
99
        return LoggerFactory.getLogger(ex.getStackTrace()[0].getClassName());
100
    }
101
}
server/src/main/java/org/danekja/ymanager/ws/rest/RESTGetHandler.java
1
package org.danekja.ymanager.ws.rest;
2

  
3
import java.lang.FunctionalInterface;
4

  
5
@FunctionalInterface
6
public interface RESTGetHandler<T> {
7
    T get() throws Exception;
8
}
server/src/main/java/org/danekja/ymanager/ws/rest/RESTInvokeHandler.java
1
package org.danekja.ymanager.ws.rest;
2

  
3
@FunctionalInterface
4
public interface RESTInvokeHandler<T> {
5
    void invoke() throws Exception;
6
}
server/src/main/java/org/danekja/ymanager/ws/rest/UserController.java
3 3
import org.danekja.ymanager.business.UserManager;
4 4
import org.danekja.ymanager.domain.Status;
5 5
import org.danekja.ymanager.domain.User;
6
import org.danekja.ymanager.dto.BasicProfileUser;
6 7
import org.danekja.ymanager.dto.FullUserProfile;
7
import org.danekja.ymanager.util.localization.Language;
8
import org.danekja.ymanager.ws.rest.exceptions.NotFoundException;
9 8
import org.springframework.beans.factory.annotation.Autowired;
10
import org.springframework.http.ResponseEntity;
11 9
import org.springframework.security.authentication.AnonymousAuthenticationToken;
12 10
import org.springframework.security.core.Authentication;
13 11
import org.springframework.web.bind.annotation.*;
14 12

  
13
import java.util.List;
14

  
15 15
/**
16 16
 * Controller for Users collection of WS API.
17 17
 */
18 18
@RestController
19 19
@RequestMapping("/users")
20
public class UserController extends BaseController {
20
public class UserController {
21 21

  
22 22
    private final UserManager userManager;
23 23

  
......
27 27
    }
28 28

  
29 29
    @GetMapping
30
    public ResponseEntity users(
30
    public List<BasicProfileUser> users(
31 31
            @RequestParam(value = "lang", required = false) String lang,
32 32
            @RequestParam(value = "status", required = false) String status) {
33
        return handle(Language.getLanguage(lang), () ->
34
                userManager.getUsers(Status.getStatus(status))
35
        );
33
        return userManager.getUsers(Status.getStatus(status));
36 34
    }
37 35

  
38 36

  
......
58 56
    public FullUserProfile getUserProfile(@PathVariable("id") Long id) throws Exception {
59 57
        User u = userManager.getUser(id);
60 58

  
61
        if (u == null) {
62
            throw new NotFoundException();
63
        }
64

  
65 59
        return new FullUserProfile(u);
66 60
    }
67 61
}
server/src/main/java/org/danekja/ymanager/ws/rest/exceptions/NotFoundException.java
1
package org.danekja.ymanager.ws.rest.exceptions;
2

  
3
public class NotFoundException extends Exception {
4
}
server/src/main/java/org/danekja/ymanager/ws/rest/exceptions/RESTFullException.java
1
package org.danekja.ymanager.ws.rest.exceptions;
2

  
3
public class RESTFullException extends Exception {
4

  
5
    private final String messageKey;
6

  
7
    public RESTFullException(String message, String messageKey) {
8
        super(message);
9
        this.messageKey = messageKey;
10
    }
11

  
12
    @Override
13
    public String getLocalizedMessage() {
14
        return messageKey;
15
    }
16
}

Také k dispozici: Unified diff