-
Notifications
You must be signed in to change notification settings - Fork 0
Dev #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Dev #5
Changes from all commits
7c51bc6
730e3ce
65ff446
014333c
baa3a67
3c995d9
7cbde13
97d4f36
1dfd9cc
b2e0a60
56b968a
870f772
5f96dee
425f6a9
57fb256
7fa57aa
9281242
0956d78
fc3bb54
118acf5
965277b
3705d33
a8bb7ed
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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%"/> |
| 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; | ||
| } |
| 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)) { | ||
| compressor = new CompressZIP(); | ||
| } | ||
| return compressor; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| package edu.najah.cap.compress; | ||
|
|
||
| public enum CompressType { | ||
| ZIP | ||
| } |
| 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
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. useless try-catch and throw |
||
| }catch (NullPointerException e){ | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. very bad practice to have an empty catch block |
||
| } | ||
| } | ||
| log(Level.INFO, "Compressing Completed", "CompressZIP", "compress"); | ||
| } | ||
| } | ||
| } | ||
| 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; | ||
|
|
||
| } |
| 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"; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| } | ||
| } | ||
| } | ||
|
|
||
There was a problem hiding this comment.
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