Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*.zip
*.tar.gz
*.rar
*.txt

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
Expand Down
Binary file added AdvancedProjectClassDiagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions AdvancedProjectTeam15.iml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="itextpdf.maven.itextdoc" level="project" />
</component>
</module>
73 changes: 72 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,72 @@
# AdvancedProjectTeam15
# AdvancedProjectTeam15

This project represents a component of a larger application.
This component has 2 main functionalities:
1. Export user data from data services
2. Delete user data from data services

## Export
So we made adding exporting methods easy, but at start we have only two methods, 1 implemented and 1 waiting to be implemented.
The implemented method is the one that downloads the data to the user's device.
Firstly, the function calls the export method for each service, each service stores the data as a text file then calls the PDFConvertor to convert the text files into PDF files.
After that, the function calls the zip method to zip all the PDF files into one zip file.

This method takes 2 parameters:
1. The username of the user that wants to export his data, of course it considers the user's powers.
2. The path of the folder that the user wants to download his data to.

So we made adding exporting methods easy by implementing the factory design pattern, that also applies for the export methods for each service, which makes adding a new services to the system requires the minimum amount of changes ,also we considered that there are more than one type of compression, so we made the compression method as a factory design pattern, so we can add more compression methods easily.

You can find the result in the directory called "output" in the src directory.
## Delete
We have two types of delete methods.
1. Hard delete: which deletes all the user's data including the user's account, it also prevents creating a new account using the deleted account's username, this was implemented by adding the deletedUserArchive class.
2. Soft delete: which deletes all the user's data except the user's account.

We also made adding deleting methods easy by implementing the factory design pattern.
We used the iterator design pattern to iterate over some services' data to preform clean delete to avoid any errors.

## Using Services
So we noticed that every service has its own interface, this gave us a sign that the system expects adding more services for the service family in the future.
This is why we added a factory for each service and used it in our system without touching the services themselves.
We also noticed that the user profile service didn't consider two important thins:
1. Adding or updating a user with a blank username.
2. Adding or updating a user with a username that has been hard deleted.
3. Adding or updating a user with a username that already exists, since it stores in a hash map, so it will overwrite the old user without even notifying.

And since we don't want to modify the user profile service, we used the proxy design pattern to create proxy user profile service that handles the previous cases as follows:
1. Overriding the add and update methods to throw BlankUsernameException if the username is blank.
2. Overriding the add and update methods to throw UserDeletedException if the username is in the deleted users archive.
3. Overriding the add method to throw UserAlreadyExistsException if the username already exists.

## Exceptions
We tried our best to handle any expected exceptions and avoid any unexpected behavior.

* Built-in Exceptions: we handled each as it should be handled.

* Services exceptions: we handled the exceptions that may occur in the services, mainly:
* UserNotFoundException, BadRequestException: we handled them by keep throwing them until they reach the application function, since the application function is the one responsible for the wrong input.
* SystemBusyException: We handled it by retrying the request until it succeeds.

* Custom Exceptions: we found that in some scenarios build in exceptions where not suitable, so we created our own:
* BlankUsernameException: we throw it when the user tries to add or update a user with a blank username.
* FileDeleteException: we throw it when the system fails to delete a file during the export operation.
* UnqualifiedUserException: we throw it when the user tries to export data that he is not qualified to export.
* UserAlreadyExistsException: we throw it when the user tries to add a user with a username that already exists.
* UserDeletedException: we throw it when the user tries to add or update a user with a username that has been hard deleted.

## Logging
Logging makes everything easier, so adding logging to our system was a must.
To make it easier, we created our own logger class that handles all the logging operations and stores the logs in a text file in the "out/production/AdvancedProjectTeam15/edu/najah/cap/logs" directory.
This way, we can easily change the logging options in one place.
Other methods can use the logger by calling the static method "log" in the logger class.

## Conclusion
We mainly used the following design patterns:
* Factory design pattern: to make the system more flexible and easier to add new services, export methods, delete methods, and compression methods.
* Proxy design pattern: to enhance the user profile service by handling some cases that the user profile service didn't handle without modifying the user profile service itself.
* Iterator design pattern: to iterate over some services' data to preform clean delete to avoid any errors.
* Singleton design pattern: we combine the singleton design pattern with the proxy design pattern to enhance the performance of the system by creating only one instance of the user profile service in the proxy user profile service.

## Class Diagram
<img src="./AdvancedProjectClassDiagram.png" width="100%"/>
8 changes: 6 additions & 2 deletions src/edu/najah/cap/activity/IUserActivityService.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package edu.najah.cap.activity;

import edu.najah.cap.exceptions.BadRequestException;
import edu.najah.cap.exceptions.NotFoundException;
import edu.najah.cap.exceptions.SystemBusyException;

import java.util.List;

public interface IUserActivityService {

void addUserActivity(UserActivity userActivity);

List<UserActivity> getUserActivity(String userId);
List<UserActivity> getUserActivity(String userId) throws SystemBusyException, BadRequestException, NotFoundException;

void removeUserActivity(String userId, String id);
void removeUserActivity(String userId, String id) throws SystemBusyException, BadRequestException, NotFoundException;
}
19 changes: 17 additions & 2 deletions src/edu/najah/cap/activity/UserActivityService.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
package edu.najah.cap.activity;

import edu.najah.cap.exceptions.BadRequestException;
import edu.najah.cap.exceptions.NotFoundException;
import edu.najah.cap.exceptions.SystemBusyException;
import edu.najah.cap.exceptions.Util;
import edu.najah.cap.payment.Transaction;

import java.time.Instant;
import java.util.*;

public class UserActivityService implements IUserActivityService {
Expand All @@ -10,12 +17,20 @@ public void addUserActivity(UserActivity userActivity) {
}

@Override
public List<UserActivity> getUserActivity(String userId) {
public List<UserActivity> getUserActivity(String userId) throws SystemBusyException, BadRequestException, NotFoundException {
Util.validateUserName(userId);
if (!userActivityMap.containsKey(userId)) {
throw new NotFoundException("User does not exist");
}
return userActivityMap.get(userId);
}

@Override
public void removeUserActivity(String userId, String id) {
public void removeUserActivity(String userId, String id) throws SystemBusyException, BadRequestException, NotFoundException {
Util.validateUserName(userId);
if (!userActivityMap.containsKey(userId)) {
throw new NotFoundException("User does not exist");
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Expand Down
11 changes: 11 additions & 0 deletions src/edu/najah/cap/compress/CompressFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package edu.najah.cap.compress;

public class CompressFactory {
public static ICompress getCompressType(CompressType type) {
ICompress compressor = null;
if (type.equals(CompressType.ZIP)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we usually prefer to write it like this CompressType.ZIP.equals( .. to avoid null pointer exception

compressor = new CompressZIP();
}
return compressor;
}
}
5 changes: 5 additions & 0 deletions src/edu/najah/cap/compress/CompressType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package edu.najah.cap.compress;

public enum CompressType {
ZIP
}
52 changes: 52 additions & 0 deletions src/edu/najah/cap/compress/CompressZIP.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package edu.najah.cap.compress;

import edu.najah.cap.exceptions.FileDeletionException;

import java.io.*;
import java.util.List;
import java.util.logging.Level;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import static edu.najah.cap.logs.MyLogging.log;

public class CompressZIP implements ICompress {

@Override
public void compress(String path, List<String> files) throws IOException {
log(Level.INFO, "Compressing to ZIP", "CompressZIP", "compress");
String outputPath = path + "UserData.zip";
try(ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(outputPath))) {
for (String f : files) {
try {
File file = new File(f);
ZipEntry entry = new ZipEntry(file.getName());
zipOutputStream.putNextEntry(entry);
if (!file.exists()) {
throw new FileNotFoundException("File not found: " + f);
}
try (FileInputStream fileInputStream = new FileInputStream(file)) {
int len;
byte[] data = new byte[1024];
while ((len = fileInputStream.read(data)) != -1) {
zipOutputStream.write(data, 0, len);
}
} catch (IOException e) {
log(Level.SEVERE, e.getMessage(), "CompressZIP", "compress");
} finally {
zipOutputStream.closeEntry();
}
Comment on lines +22 to +38
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

none primitive method, you need to extract it

try {
if (!file.delete()) {
throw new FileDeletionException("File Deletion Failed");
}
} catch (FileDeletionException e) {
log(Level.SEVERE, e.getMessage(), "CompressZIP", "compress");
}
Comment on lines +39 to +45
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useless try-catch and throw

}catch (NullPointerException e){
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very bad practice to have an empty catch block
instead, created new custom exception such as CompressZIPExecption as throw it

}
}
log(Level.INFO, "Compressing Completed", "CompressZIP", "compress");
}
}
}
9 changes: 9 additions & 0 deletions src/edu/najah/cap/compress/ICompress.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package edu.najah.cap.compress;

import java.io.IOException;
import java.util.List;

public interface ICompress {
public void compress(String path, List<String> files) throws IOException;

}
47 changes: 47 additions & 0 deletions src/edu/najah/cap/convertor/PDFConvertor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package edu.najah.cap.convertor;

import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.pdf.PdfWriter;
import edu.najah.cap.exceptions.FileDeletionException;

import java.io.*;
import java.util.logging.Level;

import static edu.najah.cap.logs.MyLogging.log;

public class PDFConvertor {
public static String ConvertToPdf(String path) throws IOException {
log(Level.INFO, "Converting to PDF", "PDFConvertor", "ConvertTOPdf");
String textFile = path;
String pdfFile = path.substring(0, path.length() - 4) + ".pdf";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4: magic number

try {
Document document = new Document();
PdfWriter.getInstance(document, new FileOutputStream(pdfFile));
document.open();
BufferedReader bufferedReader = new BufferedReader(new FileReader(textFile));
String line;
while ((line = bufferedReader.readLine()) != null) {
document.add(new Paragraph(line));
}
bufferedReader.close();
document.close();
Comment on lines +28 to +29
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be in finally block


try {
File file = new File(textFile);
if (!file.delete()) {
throw new FileDeletionException("File Deletion Failed");
}
} catch (FileDeletionException e) {
log(Level.SEVERE, e.getMessage(), "PDFConvertor", "ConvertTOPdf");
}
Comment on lines +31 to +38
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

log(Level.INFO, "Converting Completed", "PDFConvertor", "ConvertTOPdf");
return pdfFile;
} catch(DocumentException e) {
log(Level.SEVERE, e.getMessage(), "PDFConvertor", "ConvertTOPdf");
return null;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not return null, because it will cause null pointer exception later, throw exception instead

}
}
}

Loading