Features Required:
User Authentication and Authorization:
- Description: Implement a user management system where users can register, log in, and have roles assigned (e.g., admin, regular user). Authorization controls access to files and operations based on user roles and permissions.
File and Folder Management:
- Description: Allow users to create, rename, delete, upload, and download files and folders. Files and folders are organized hierarchically, resembling a tree structure.
File Sharing:
- Description: Users can share files and folders with other users, assigning specific permissions such as read or write access.
Version Control:
- Description: Maintain versions of files whenever changes are made, allowing users to revert to previous versions if needed.
Search Functionality:
- Description: Enable users to search for files and folders by name within their accessible directories.
Storage Quota:
- Description: Implement storage limits per user, tracking the total storage used and preventing actions that exceed the quota.
Design Patterns Used:
Singleton Pattern:
Usage: Ensure only one instance of the
FileSystemManager
exists to manage the file system globally.Reason: Centralizes file system management, preventing inconsistencies and ensuring synchronized access.
Factory Pattern:
Usage: Use a
FileSystemFactory
to createFile
andFolder
objects.Reason: Encapsulates the creation logic, making it easier to manage object creation and extensions.
Composite Pattern:
Usage: Implement
File
andFolder
classes that both extend a commonFileSystemComponent
interface.Reason: Allows clients to treat individual objects and compositions uniformly, simplifying file system operations.
Decorator Pattern:
Usage: Use
PermissionsDecorator
to add permissions dynamically to files and folders.Reason: Enhances objects with additional responsibilities without modifying their structure.
Observer Pattern:
Usage: Implement observers that notify users when shared files are updated.
Reason: Provides a mechanism for automatic updates, improving collaboration features.
Strategy Pattern:
Usage: Define different storage strategies (e.g., local storage, cloud storage) via a
StorageStrategy
interface.Reason: Enables switching between different storage implementations without affecting the system's core logic.
Proxy Pattern:
Usage: Use
FileSystemProxy
to control access to files and folders based on user permissions.Reason: Adds a layer of security by controlling object access, enforcing permission checks.
Command Pattern:
Usage: Implement undo/redo functionality for file operations using command objects.
Reason: Encapsulates requests as objects, allowing for operation sequencing, undoing, and redoing actions.
Template Method Pattern:
Usage: Define an abstract
FileOperation
class with a template method for file operations.Reason: Allows subclasses to redefine certain steps of an algorithm without changing its structure.
Algorithms Involved:
Tree Traversal Algorithms:
- Used for navigating and managing the hierarchical file system (e.g., depth-first search for searching files).
Hashing:
- Utilized for quick lookup of files and folders using hash maps.
Access Control Algorithms:
- Implemented to check user permissions before performing operations on files and folders.
Version Control Algorithms:
- Manage file versions by keeping a history of changes and allowing retrieval of previous versions.
Diagrams
Code (Java):
// FileSystemComponent.java
public interface FileSystemComponent {
String getName();
void setName(String name);
void add(FileSystemComponent component) throws UnsupportedOperationException;
void remove(FileSystemComponent component) throws UnsupportedOperationException;
FileSystemComponent getChild(String name) throws UnsupportedOperationException;
void display(String indent);
}
// File.java
import java.util.ArrayList;
import java.util.List;
public class File implements FileSystemComponent {
private String name;
private String content;
private List<FileVersion> versions;
private PermissionsDecorator permissions;
public File(String name) {
this.name = name;
this.content = "";
this.versions = new ArrayList<>();
this.permissions = new PermissionsDecorator();
}
// Composite Pattern Methods
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public void add(FileSystemComponent component) {
throw new UnsupportedOperationException("Cannot add to a file.");
}
@Override
public void remove(FileSystemComponent component) {
throw new UnsupportedOperationException("Cannot remove from a file.");
}
@Override
public FileSystemComponent getChild(String name) {
throw new UnsupportedOperationException("Files do not contain children.");
}
@Override
public void display(String indent) {
System.out.println(indent + "File: " + name);
}
// File-Specific Methods
public void writeContent(String content) {
this.content = content;
versions.add(new FileVersion(content));
}
public String readContent() {
return content;
}
public List<FileVersion> getVersions() {
return versions;
}
public PermissionsDecorator getPermissions() {
return permissions;
}
}
// Folder.java
import java.util.HashMap;
import java.util.Map;
public class Folder implements FileSystemComponent {
private String name;
private Map<String, FileSystemComponent> children;
private PermissionsDecorator permissions;
public Folder(String name) {
this.name = name;
this.children = new HashMap<>();
this.permissions = new PermissionsDecorator();
}
// Composite Pattern Methods
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public void add(FileSystemComponent component) {
children.put(component.getName(), component);
}
@Override
public void remove(FileSystemComponent component) {
children.remove(component.getName());
}
@Override
public FileSystemComponent getChild(String name) {
return children.get(name);
}
@Override
public void display(String indent) {
System.out.println(indent + "Folder: " + name);
for (FileSystemComponent component : children.values()) {
component.display(indent + " ");
}
}
public Map<String, FileSystemComponent> getChildren() {
return children;
}
public PermissionsDecorator getPermissions() {
return permissions;
}
}
// PermissionsDecorator.java
import java.util.HashMap;
import java.util.Map;
public class PermissionsDecorator {
private Map<User, Permission> userPermissions;
public PermissionsDecorator() {
this.userPermissions = new HashMap<>();
}
public void setPermission(User user, Permission permission) {
userPermissions.put(user, permission);
}
public Permission getPermission(User user) {
return userPermissions.getOrDefault(user, Permission.NONE);
}
public enum Permission {
READ, WRITE, NONE
}
}
// User.java
public class User {
private String username;
private String password;
private Role role;
public enum Role {
ADMIN, USER
}
public User(String username, String password, Role role) {
this.username = username;
this.password = password;
this.role = role;
}
// Getters
public String getUsername() {
return username;
}
public Role getRole() {
return role;
}
// Authentication Method
public boolean authenticate(String password) {
return this.password.equals(password);
}
}
// FileSystemManager.java
public class FileSystemManager {
private static FileSystemManager instance;
private Folder root;
private StorageStrategy storageStrategy;
private FileSystemManager() {
root = new Folder("root");
storageStrategy = new LocalStorageStrategy(); // Default Strategy
}
public static synchronized FileSystemManager getInstance() {
if (instance == null) {
instance = new FileSystemManager();
}
return instance;
}
public Folder getRoot() {
return root;
}
public void setStorageStrategy(StorageStrategy strategy) {
this.storageStrategy = strategy;
}
public StorageStrategy getStorageStrategy() {
return storageStrategy;
}
}
// FileVersion.java
public class FileVersion {
private String content;
private long timestamp;
public FileVersion(String content) {
this.content = content;
this.timestamp = System.currentTimeMillis();
}
public String getContent() {
return content;
}
public long getTimestamp() {
return timestamp;
}
}
// Observer.java
public interface Observer {
void update(String message);
}
// Subject.java
import java.util.ArrayList;
import java.util.List;
public class Subject {
private List<Observer> observers = new ArrayList<>();
public void attach(Observer observer) {
observers.add(observer);
}
public void detach(Observer observer) {
observers.remove(observer);
}
protected void notifyObservers(String message) {
for (Observer observer : observers) {
observer.update(message);
}
}
}
// StorageStrategy.java
public interface StorageStrategy {
void saveFile(File file);
File loadFile(String fileName);
}
// LocalStorageStrategy.java
public class LocalStorageStrategy implements StorageStrategy {
@Override
public void saveFile(File file) {
// Implement local storage saving logic
System.out.println("Saving file locally: " + file.getName());
}
@Override
public File loadFile(String fileName) {
// Implement local storage loading logic
System.out.println("Loading file locally: " + fileName);
return new File(fileName);
}
}
// FileSystemProxy.java
public class FileSystemProxy implements FileSystemComponent {
private FileSystemComponent realComponent;
private User currentUser;
public FileSystemProxy(FileSystemComponent component, User user) {
this.realComponent = component;
this.currentUser = user;
}
@Override
public String getName() {
return realComponent.getName();
}
@Override
public void setName(String name) {
if (hasWritePermission()) {
realComponent.setName(name);
} else {
throw new SecurityException("No write permission for user: " + currentUser.getUsername());
}
}
@Override
public void add(FileSystemComponent component) {
if (hasWritePermission()) {
realComponent.add(component);
} else {
throw new SecurityException("No write permission for user: " + currentUser.getUsername());
}
}
@Override
public void remove(FileSystemComponent component) {
if (hasWritePermission()) {
realComponent.remove(component);
} else {
throw new SecurityException("No write permission for user: " + currentUser.getUsername());
}
}
@Override
public FileSystemComponent getChild(String name) {
if (hasReadPermission()) {
return realComponent.getChild(name);
} else {
throw new SecurityException("No read permission for user: " + currentUser.getUsername());
}
}
@Override
public void display(String indent) {
if (hasReadPermission()) {
realComponent.display(indent);
} else {
throw new SecurityException("No read permission for user: " + currentUser.getUsername());
}
}
private boolean hasReadPermission() {
PermissionsDecorator.Permission permission = realComponent.getPermissions().getPermission(currentUser);
return permission == PermissionsDecorator.Permission.READ || permission == PermissionsDecorator.Permission.WRITE;
}
private boolean hasWritePermission() {
PermissionsDecorator.Permission permission = realComponent.getPermissions().getPermission(currentUser);
return permission == PermissionsDecorator.Permission.WRITE;
}
}
// FileOperation.java
public abstract class FileOperation {
public final void execute() {
if (validate()) {
performOperation();
logOperation();
} else {
System.out.println("Validation failed. Operation aborted.");
}
}
protected abstract boolean validate();
protected abstract void performOperation();
protected void logOperation() {
System.out.println("Operation performed: " + this.getClass().getSimpleName());
}
}
// CreateFileOperation.java
public class CreateFileOperation extends FileOperation {
private Folder folder;
private String fileName;
public CreateFileOperation(Folder folder, String fileName) {
this.folder = folder;
this.fileName = fileName;
}
@Override
protected boolean validate() {
return folder.getChild(fileName) == null;
}
@Override
protected void performOperation() {
folder.add(new File(fileName));
}
}
// FileSystemSearch.java
public class FileSystemSearch {
public FileSystemComponent search(FileSystemComponent component, String name) {
if (component.getName().equals(name)) {
return component;
}
if (component instanceof Folder) {
Folder folder = (Folder) component;
for (FileSystemComponent child : folder.getChildren().values()) {
FileSystemComponent found = search(child, name);
if (found != null) {
return found;
}
}
}
return null;
}
}
// Main.java
public class Main {
public static void main(String[] args) {
// Create Users
User admin = new User("admin", "admin123", User.Role.ADMIN);
User user1 = new User("user1", "password1", User.Role.USER);
User user2 = new User("user2", "password2", User.Role.USER);
// Get FileSystemManager Instance
FileSystemManager fsManager = FileSystemManager.getInstance();
// Create Folders and Files
Folder root = fsManager.getRoot();
root.getPermissions().setPermission(admin, PermissionsDecorator.Permission.WRITE);
root.getPermissions().setPermission(user1, PermissionsDecorator.Permission.WRITE);
// Create Files and Folders using Template Method Pattern
FileOperation createFolderOperation = new CreateFileOperation(root, "Documents");
createFolderOperation.execute();
Folder documents = (Folder) root.getChild("Documents");
documents.getPermissions().setPermission(user1, PermissionsDecorator.Permission.WRITE);
FileOperation createFileOperation = new CreateFileOperation(documents, "Resume.docx");
createFileOperation.execute();
// Use Proxy Pattern for Access Control
FileSystemProxy user1Proxy = new FileSystemProxy(documents, user1);
user1Proxy.display("");
// Version Control
File resume = (File) documents.getChild("Resume.docx");
resume.writeContent("Version 1 of Resume");
resume.writeContent("Version 2 of Resume");
// Observer Pattern for Notifications
Subject subject = new Subject();
Observer user2Observer = new Observer() {
@Override
public void update(String message) {
System.out.println("User2 received notification: " + message);
}
};
subject.attach(user2Observer);
subject.notifyObservers("Resume.docx has been updated.");
// Search Functionality
FileSystemSearch search = new FileSystemSearch();
FileSystemComponent found = search.search(root, "Resume.docx");
if (found != null) {
System.out.println("Found: " + found.getName());
} else {
System.out.println("File not found.");
}
// Storage Strategy
fsManager.setStorageStrategy(new LocalStorageStrategy());
fsManager.getStorageStrategy().saveFile(resume);
}
}
Explanation of the Code:
Composite Pattern:
File
andFolder
both implementFileSystemComponent
, allowing them to be treated uniformly.Singleton Pattern:
FileSystemManager
ensures only one instance manages the file system.Decorator Pattern:
PermissionsDecorator
adds permissions to files and folders dynamically.Observer Pattern:
Subject
andObserver
enable notifications when files are updated.Strategy Pattern:
StorageStrategy
allows for different storage mechanisms.Proxy Pattern:
FileSystemProxy
controls access based on user permissions.Command Pattern:
FileOperation
and its subclasses implement operations that can be undone/redone.Template Method Pattern:
FileOperation
defines the steps for file operations, allowing subclasses to implement specifics.Search Functionality:
FileSystemSearch
recursively searches the file system tree.
Missing Feature (Will be Covered in our Premium Course)
In above code, permission is based on users only but we need more granular level control like having access at File Level
Having different Access type like commenter, read, write like google drive have.
Multi-threading Support
Current design does not support multi-threading and distributed systems out of the box.The design and code provided earlier are intended for a single-threaded application running on a single machine. The focus was on demonstrating how to implement various features and design patterns in Java to simulate a simplified version of Google Drive. As such, the implementation lacks the necessary components to handle multi-threading and distribution across multiple systems.
To extend the current design to support multi-threading and distributed systems, several significant modifications and considerations are necessary. Covered in our Premium Course