mirror of
https://github.com/klmp200/PQT_Gestionnaire_vente_stock.git
synced 2024-11-16 21:33:21 +00:00
Merge branch 'feature/#3_Développement_code_métier_serveur' into develop
This commit is contained in:
commit
2f15f382b5
Binary file not shown.
Binary file not shown.
8
Workspace/.idea/artifacts/server_war.xml
Normal file
8
Workspace/.idea/artifacts/server_war.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<component name="ArtifactManager">
|
||||
<artifact type="war" name="server:war">
|
||||
<output-path>$PROJECT_DIR$/server/target</output-path>
|
||||
<root id="archive" name="server-1.0-SNAPSHOT.war">
|
||||
<element id="artifact" artifact-name="server:war exploded" />
|
||||
</root>
|
||||
</artifact>
|
||||
</component>
|
23
Workspace/.idea/artifacts/server_war_exploded.xml
Normal file
23
Workspace/.idea/artifacts/server_war_exploded.xml
Normal file
@ -0,0 +1,23 @@
|
||||
<component name="ArtifactManager">
|
||||
<artifact type="exploded-war" name="server:war exploded">
|
||||
<output-path>$PROJECT_DIR$/server/target/server-1.0-SNAPSHOT</output-path>
|
||||
<root id="root">
|
||||
<element id="directory" name="WEB-INF">
|
||||
<element id="directory" name="classes">
|
||||
<element id="module-output" name="server" />
|
||||
</element>
|
||||
<element id="directory" name="lib">
|
||||
<element id="archive" name="core-1.0-SNAPSHOT.jar">
|
||||
<element id="module-output" name="core" />
|
||||
</element>
|
||||
<element id="library" level="project" name="Maven: com.google.code.gson:gson:2.8.1" />
|
||||
<element id="library" level="project" name="Maven: commons-io:commons-io:2.4" />
|
||||
</element>
|
||||
</element>
|
||||
<element id="directory" name="META-INF">
|
||||
<element id="file-copy" path="$PROJECT_DIR$/server/target/server-1.0-SNAPSHOT/META-INF/MANIFEST.MF" />
|
||||
</element>
|
||||
<element id="javaee-facet-resources" facet="server/web/Web" />
|
||||
</root>
|
||||
</artifact>
|
||||
</component>
|
9
Workspace/.idea/codeStyleSettings.xml
Normal file
9
Workspace/.idea/codeStyleSettings.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectCodeStyleSettingsManager">
|
||||
<option name="PER_PROJECT_SETTINGS">
|
||||
<value />
|
||||
</option>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</component>
|
||||
</project>
|
@ -8,12 +8,13 @@
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<module name="client" />
|
||||
<module name="core" />
|
||||
<module name="server" />
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
<bytecodeTargetLevel>
|
||||
<module name="client" target="1.8" />
|
||||
<module name="core" target="1.8" />
|
||||
<module name="server" target="1.5" />
|
||||
<module name="server" target="1.8" />
|
||||
<module name="Workspace" target="1.8" />
|
||||
</bytecodeTargetLevel>
|
||||
</component>
|
||||
|
@ -1,15 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<file type="web" url="file://$PROJECT_DIR$/server" />
|
||||
</component>
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="ignoredFiles">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$/server/pom.xml" />
|
||||
</set>
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
|
@ -5,6 +5,7 @@
|
||||
<module fileurl="file://$PROJECT_DIR$/Workspace.iml" filepath="$PROJECT_DIR$/Workspace.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/client/client.iml" filepath="$PROJECT_DIR$/client/client.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/core/core.iml" filepath="$PROJECT_DIR$/core/core.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/server/server.iml" filepath="$PROJECT_DIR$/server/server.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
@ -1,6 +1,5 @@
|
||||
package com.pqt.core.entities.members;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
|
||||
@ -10,22 +9,14 @@ import java.util.Objects;
|
||||
public class Client extends PqtMember{
|
||||
|
||||
private String address;
|
||||
private Date lastUpdate;
|
||||
|
||||
public Client() {
|
||||
super(-1, PqtMemberType.CLIENT);
|
||||
}
|
||||
|
||||
public Client(int id, String address) {
|
||||
public Client(long id, String address) {
|
||||
super(id, PqtMemberType.CLIENT);
|
||||
this.address = address;
|
||||
this.lastUpdate = new Date();
|
||||
}
|
||||
|
||||
public Client(int id, String address, Date lastUpdate) {
|
||||
super(id, PqtMemberType.CLIENT);
|
||||
this.address = address;
|
||||
this.lastUpdate = lastUpdate;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
@ -36,12 +27,19 @@ public class Client extends PqtMember{
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public Date getLastUpdate() {
|
||||
return lastUpdate;
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Client client = (Client) o;
|
||||
|
||||
return address.equals(client.address) && id==client.id && type.equals(client.type);
|
||||
}
|
||||
|
||||
public void setLastUpdate(Date lastUpdate) {
|
||||
this.lastUpdate = lastUpdate;
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return address.hashCode() + type.hashCode() + Integer.class.cast(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,13 +1,11 @@
|
||||
package com.pqt.core.entities.members;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
|
||||
public class DataServer extends PqtMember{
|
||||
|
||||
private String address;
|
||||
private Date lastUpdate;
|
||||
|
||||
public DataServer() {
|
||||
super(-1, PqtMemberType.DATA_SERVER);
|
||||
@ -16,15 +14,8 @@ public class DataServer extends PqtMember{
|
||||
public DataServer(long id, String address) {
|
||||
super(id, PqtMemberType.DATA_SERVER);
|
||||
this.address = address;
|
||||
this.lastUpdate = new Date();
|
||||
}
|
||||
|
||||
public DataServer(long id, String address, Date lastUpdate) {
|
||||
super(id, PqtMemberType.DATA_SERVER);
|
||||
this.address = address;
|
||||
this.lastUpdate = lastUpdate;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
@ -33,14 +24,6 @@ public class DataServer extends PqtMember{
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public Date getLastUpdate() {
|
||||
return lastUpdate;
|
||||
}
|
||||
|
||||
public void setLastUpdate(Date lastUpdate) {
|
||||
this.lastUpdate = lastUpdate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(super.hashCode(), address);
|
||||
|
@ -7,8 +7,8 @@ import java.util.Objects;
|
||||
|
||||
public class PqtMember implements ILoggable, Serializable {
|
||||
|
||||
private long id;
|
||||
private PqtMemberType type;
|
||||
protected long id;
|
||||
protected PqtMemberType type;
|
||||
|
||||
public PqtMember() {
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.pqt.core.entities.messages;
|
||||
|
||||
import com.pqt.core.entities.members.PqtMember;
|
||||
import com.pqt.core.entities.user_account.Account;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@ -9,15 +10,19 @@ public class Message {
|
||||
private Map<String, String> fields;
|
||||
private MessageType type;
|
||||
private PqtMember emitter, receiver;
|
||||
private Account user;
|
||||
private Message replyTo;
|
||||
|
||||
public Message(MessageType type, PqtMember emitter, PqtMember receiver) {
|
||||
this(type, emitter, receiver, null);
|
||||
public Message(MessageType type, PqtMember emitter, PqtMember receiver, Account user, Message replyTo) {
|
||||
this(type, emitter, receiver, user, replyTo, null);
|
||||
}
|
||||
|
||||
public Message(MessageType type, PqtMember emitter, PqtMember receiver, Map<String, String> fields) {
|
||||
public Message(MessageType type, PqtMember emitter, PqtMember receiver, Account user, Message replyTo, Map<String, String> fields) {
|
||||
this.emitter = emitter;
|
||||
this.receiver = receiver;
|
||||
this.type = type;
|
||||
this.user = user;
|
||||
this.replyTo = replyTo;
|
||||
this.fields = new HashMap<>();
|
||||
if(fields!=null)
|
||||
for(String key : fields.keySet()){
|
||||
@ -56,10 +61,10 @@ public class Message {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if(this == obj)
|
||||
if (this == obj)
|
||||
return true;
|
||||
|
||||
if(!this.getClass().isInstance(obj))
|
||||
if (!this.getClass().isInstance(obj))
|
||||
return false;
|
||||
|
||||
Message other = Message.class.cast(obj);
|
||||
@ -68,4 +73,12 @@ public class Message {
|
||||
&& Objects.equals(this.receiver, other.receiver)
|
||||
&& Objects.equals(this.type, other.type);
|
||||
}
|
||||
|
||||
public Account getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public Message getReplyTo() {
|
||||
return replyTo;
|
||||
}
|
||||
}
|
||||
|
@ -1,38 +1,36 @@
|
||||
package com.pqt.core.entities.messages;
|
||||
|
||||
public enum MessageType {
|
||||
QUERY_CONNECT,
|
||||
ACK_CONNECT,
|
||||
ERR_CONNECT,
|
||||
REF_CONNECT,
|
||||
ERROR_QUERY,
|
||||
REFUSED_QUERY,
|
||||
|
||||
QUERY_SALE,
|
||||
ACK_SALE,
|
||||
ERR_SALE,
|
||||
REF_SALE,
|
||||
|
||||
QUERY_REVERT_SALE,
|
||||
ACK_REVERT_SALE,
|
||||
ERR_REVERT_SALE,
|
||||
REF_REVERT_SALE,
|
||||
|
||||
QUERY_LAST_SALES_LIST,
|
||||
MSG_LAST_SALES_LIST,
|
||||
|
||||
QUERY_STAT,
|
||||
MSG_STAT,
|
||||
ERR_STAT,
|
||||
REF_STAT,
|
||||
|
||||
QUERY_STOCK,
|
||||
MSG_STOCK,
|
||||
ERR_STOCK,
|
||||
REF_STOCK,
|
||||
|
||||
QUERY_LOGIN,
|
||||
ACK_LOGIN,
|
||||
ERR_LOGIN,
|
||||
REF_LOGIN,
|
||||
QUERY_ACCOUNT_LIST,
|
||||
MSG_ACCOUNT_LIST,
|
||||
|
||||
QUERY_CONNECT_ACCOUNT,
|
||||
ACK_CONNECT_ACCOUNT,
|
||||
|
||||
QUERY_UPDATE,
|
||||
ACK_UPDATE,
|
||||
ERR_UPDATE,
|
||||
REF_UPDATE
|
||||
|
||||
QUERY_PING,
|
||||
ACK_PING,
|
||||
|
||||
QUERY_CONFIG_LIST,
|
||||
MSG_CONFIG_LIST
|
||||
}
|
||||
|
@ -34,10 +34,14 @@ public class Product implements ILoggable, Serializable{
|
||||
this.category = category;
|
||||
this.components = new ArrayList<>();
|
||||
if(components!=null){
|
||||
this.components.addAll(components);
|
||||
components.stream().forEach(p->this.components.add(new Product(p)));
|
||||
}
|
||||
}
|
||||
|
||||
public Product(Product p) {
|
||||
this(p.id, p.name, p.amountRemaining, p.amountSold, p.sellable, p.price, p.components, p.category);
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
@ -1,19 +0,0 @@
|
||||
package com.pqt.core.entities.query;
|
||||
|
||||
public class ConnectQuery extends SimpleQuery {
|
||||
|
||||
private String serverAddress;
|
||||
|
||||
public ConnectQuery(String serverAddress) {
|
||||
super(QueryType.CONNECT);
|
||||
this.serverAddress = serverAddress;
|
||||
}
|
||||
|
||||
public String getServerAddress() {
|
||||
return serverAddress;
|
||||
}
|
||||
|
||||
public void setServerAddress(String serverAddress) {
|
||||
this.serverAddress = serverAddress;
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
package com.pqt.core.entities.query;
|
||||
|
||||
import com.pqt.core.entities.user_account.Account;
|
||||
|
||||
public class LogQuery extends SimpleQuery {
|
||||
|
||||
private Account account;
|
||||
|
||||
private boolean newDesiredState;
|
||||
|
||||
public LogQuery(Account account, boolean newDesiredState) {
|
||||
super(QueryType.LOG);
|
||||
this.account = account;
|
||||
this.newDesiredState = newDesiredState;
|
||||
}
|
||||
|
||||
public Account getAccount() {
|
||||
return account;
|
||||
}
|
||||
|
||||
public void setAccount(Account account) {
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
public boolean isNewDesiredState() {
|
||||
return newDesiredState;
|
||||
}
|
||||
|
||||
public void setNewDesiredState(boolean newDesiredState) {
|
||||
this.newDesiredState = newDesiredState;
|
||||
}
|
||||
}
|
@ -109,10 +109,10 @@ public class Sale implements ILoggable, Serializable{
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if(this == obj)
|
||||
if (this == obj)
|
||||
return true;
|
||||
|
||||
if(!this.getClass().isInstance(obj))
|
||||
if (!this.getClass().isInstance(obj))
|
||||
return false;
|
||||
|
||||
Sale other = Sale.class.cast(obj);
|
||||
@ -123,4 +123,18 @@ public class Sale implements ILoggable, Serializable{
|
||||
&& Objects.equals(this.orderedWith, other.orderedWith)
|
||||
&& Objects.equals(this.type, other.type);
|
||||
}
|
||||
|
||||
public double getTotalPrice() {
|
||||
if(type.getPriceMultiplier()==0)
|
||||
return 0;
|
||||
return getTotalWorth()*type.getPriceMultiplier();
|
||||
}
|
||||
|
||||
public double getTotalWorth(){
|
||||
double totalWorth = 0;
|
||||
for(Product product : this.products.keySet()){
|
||||
totalWorth+=product.getPrice()*(double)this.products.get(product);
|
||||
}
|
||||
return totalWorth;
|
||||
}
|
||||
}
|
||||
|
@ -4,5 +4,15 @@ package com.pqt.core.entities.sale;
|
||||
* Created by Notmoo on 18/07/2017.
|
||||
*/
|
||||
public enum SaleType {
|
||||
CASH, BANK_CHECK, STUDENT_ASSOCIATION_ACCOUNT, OFFERED_GUEST, OFFERED_STAFF_MEMBER
|
||||
CASH(1), BANK_CHECK(1), STUDENT_ASSOCIATION_ACCOUNT(1), OFFERED_GUEST(0), OFFERED_STAFF_MEMBER(0);
|
||||
|
||||
private double priceMultiplier;
|
||||
|
||||
SaleType(double priceMultiplier) {
|
||||
this.priceMultiplier = priceMultiplier;
|
||||
}
|
||||
|
||||
public double getPriceMultiplier() {
|
||||
return priceMultiplier;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,12 @@
|
||||
package com.pqt.core.entities.server_config;
|
||||
|
||||
public enum ConfigFields {
|
||||
ALLOW_SALE_COMMIT,
|
||||
ALLOW_SALE_REVERT,
|
||||
|
||||
ALLOW_STOCK_VIEW,
|
||||
ALLOW_STOCK_UPDATE,
|
||||
|
||||
ALLOW_ACCOUNT_CONNECT,
|
||||
ALLOW_ACCOUNT_MODIFICATION
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package com.pqt.core.entities.server_config;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ServerConfig {
|
||||
|
||||
private Map<ConfigFields, Boolean> fields;
|
||||
|
||||
public ServerConfig() {
|
||||
}
|
||||
|
||||
public ServerConfig(Map<ConfigFields, Boolean> fields) {
|
||||
this.fields = fields;
|
||||
}
|
||||
|
||||
public ServerConfig(ConfigFields... configFields) {
|
||||
fields = new HashMap<>();
|
||||
Arrays.stream(configFields).forEach(field->fields.put(field, true));
|
||||
|
||||
EnumSet.allOf(ConfigFields.class).stream().filter(field->!fields.containsKey(field)).forEach(field->fields.put(field, false));
|
||||
}
|
||||
|
||||
public Map<ConfigFields, Boolean> getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
||||
public void setFields(Map<ConfigFields, Boolean> fields) {
|
||||
this.fields = fields;
|
||||
}
|
||||
|
||||
public boolean isSupported(ConfigFields field){
|
||||
return fields.containsKey(field) && fields.get(field);
|
||||
}
|
||||
|
||||
public void switchFieldValue(ConfigFields field){
|
||||
if(fields.containsKey(field)){
|
||||
fields.replace(field, !fields.get(field));
|
||||
}else{
|
||||
fields.put(field, true);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean add(ConfigFields field, boolean value){
|
||||
if(!fields.containsKey(field)){
|
||||
fields.put(field, value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -10,31 +10,19 @@ import java.util.Objects;
|
||||
* Created by Notmoo on 18/07/2017.
|
||||
*/
|
||||
public class Account implements ILoggable, Serializable {
|
||||
private int id;
|
||||
private String username;
|
||||
private String passwordHash;
|
||||
private Date creationDate;
|
||||
private String password;
|
||||
private AccountLevel permissionLevel;
|
||||
|
||||
public Account() {
|
||||
}
|
||||
|
||||
public Account(int id, String username, String passwordHash, Date creationDate, AccountLevel permissionLevel) {
|
||||
this.id = id;
|
||||
public Account(String username, String password, AccountLevel permissionLevel) {
|
||||
this.username = username;
|
||||
this.passwordHash = passwordHash;
|
||||
this.creationDate = creationDate;
|
||||
this.password = password;
|
||||
this.permissionLevel = permissionLevel;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
@ -43,20 +31,12 @@ public class Account implements ILoggable, Serializable {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPasswordHash() {
|
||||
return passwordHash;
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPasswordHash(String passwordHash) {
|
||||
this.passwordHash = passwordHash;
|
||||
}
|
||||
|
||||
public Date getCreationDate() {
|
||||
return creationDate;
|
||||
}
|
||||
|
||||
public void setCreationDate(Date creationDate) {
|
||||
this.creationDate = creationDate;
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public AccountLevel getPermissionLevel() {
|
||||
|
@ -1,8 +1,21 @@
|
||||
package com.pqt.core.entities.user_account;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Created by Notmoo on 18/07/2017.
|
||||
*/
|
||||
public enum AccountLevel {
|
||||
GUEST, STAFF, WAITER, MASTER
|
||||
LOWEST, GUEST, STAFF, WAITER, MASTER;
|
||||
|
||||
public static AccountLevel getLowest(){
|
||||
return Arrays.stream(AccountLevel.values()).sorted(Comparator.naturalOrder()).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
public static AccountLevel getHighest(){
|
||||
return Arrays.stream(AccountLevel.values()).sorted(Comparator.reverseOrder()).findFirst().orElse(null);
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
<modules>
|
||||
<module>core</module>
|
||||
<module>client</module>
|
||||
<module>server</module>
|
||||
</modules>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
@ -22,6 +23,13 @@
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-war-plugin</artifactId>
|
||||
<configuration>
|
||||
<failOnMissingWebXml>false</failOnMissingWebXml>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
37
Workspace/server/pom.xml
Normal file
37
Workspace/server/pom.xml
Normal file
@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>Main</artifactId>
|
||||
<groupId>com.pqt</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>war</packaging>
|
||||
|
||||
<artifactId>server</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<!-- https://mvnrepository.com/artifact/javax/javaee-api -->
|
||||
<dependency>
|
||||
<groupId>javax</groupId>
|
||||
<artifactId>javaee-api</artifactId>
|
||||
<version>7.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.pqt</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>2.4</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -0,0 +1,28 @@
|
||||
package com.pqt.server.controller;
|
||||
|
||||
import com.pqt.core.entities.messages.Message;
|
||||
|
||||
/**
|
||||
* Cette interface définit le type général correspondant à un élément censé traiter les objets de la classe {@link Message} arrivant au serveur.
|
||||
*
|
||||
* @author Guillaume "Cess" Prost
|
||||
*/
|
||||
public interface IMessageHandler {
|
||||
/**
|
||||
* Traite le message {@code message} passé en paramètre. Renvoie un message de réponse en tant que retour de méthode.
|
||||
* <p/>
|
||||
* Cette méthode doit toujours renvoyer un objet message <b>autre que {@code null}</b>. Un message de type
|
||||
* {@link com.pqt.core.entities.messages.MessageType#ERROR_QUERY} ou de type
|
||||
* {@link com.pqt.core.entities.messages.MessageType#REFUSED_QUERY} doit-être renvoyé si le message donné ne peut
|
||||
* être pris en charge.<br/>
|
||||
* Cela signifie aussi que cette méthode <b>ne doit pas lever d'exception</b>, et que ces dernières doivent être
|
||||
* gérées en interne.
|
||||
* <p/>
|
||||
* Pour plus de détail sur les messages, leurs significations et les réponses attendues, voir la documentation du
|
||||
* projet.
|
||||
* @param message Objet de la classe {@link Message} à traiter.
|
||||
*
|
||||
* @return Objet de la classe {@link Message} correspondant à la réponse au paramètre {@code message}.
|
||||
*/
|
||||
Message handleMessage(Message message);
|
||||
}
|
@ -0,0 +1,349 @@
|
||||
package com.pqt.server.controller;
|
||||
|
||||
import com.pqt.core.communication.GSonMessageToolFactory;
|
||||
import com.pqt.core.communication.IMessageToolFactory;
|
||||
import com.pqt.core.entities.messages.Message;
|
||||
import com.pqt.core.entities.messages.MessageType;
|
||||
import com.pqt.core.entities.product.LightweightProduct;
|
||||
import com.pqt.core.entities.product.Product;
|
||||
import com.pqt.core.entities.product.ProductUpdate;
|
||||
import com.pqt.core.entities.sale.Sale;
|
||||
import com.pqt.core.entities.server_config.ServerConfig;
|
||||
import com.pqt.core.entities.user_account.Account;
|
||||
import com.pqt.core.entities.user_account.AccountLevel;
|
||||
import com.pqt.server.exception.ServerQueryException;
|
||||
import com.pqt.server.module.account.AccountService;
|
||||
import com.pqt.server.module.sale.SaleService;
|
||||
import com.pqt.server.module.state.ServerStateService;
|
||||
import com.pqt.server.module.statistics.StatisticsService;
|
||||
import com.pqt.server.module.stock.StockService;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
//TODO Paramétrer les supports de query et leurs permissions via un meilleur système (config file, etc ...)
|
||||
/**
|
||||
* Implémentation de l'interface {@link IMessageHandler}. Cette classe définit le gestionnaire de message par défaut du
|
||||
* serveur de données du projet PQT.
|
||||
* <p/>
|
||||
* Liste des requêtes supportées :<br/>
|
||||
* <ul>
|
||||
* <li>QUERY_STOCK (WAITER)</li>
|
||||
* <li>QUERY_SALE (WAITER)</li>
|
||||
* <li>QUER_STAT (WAITER)</li>
|
||||
* <li>QUERY_UPDATE (MASTER)</li>
|
||||
* <li>QUERY_ACCOUNT_LIST (NONE)</li>
|
||||
* <li>QUERY_CONNECT_ACCOUNT (NONE)</li>
|
||||
* <li>QUERY_PING (NONE)</li>
|
||||
* <li>QUERY_CONFIG_LIST (NONE)</li>
|
||||
* </ul>
|
||||
* <p/>
|
||||
* Liste des requêtes non-supportées :<br/>
|
||||
* <ul>
|
||||
* <li>QUERY_REVERT_SALE</li>
|
||||
* <li>QUERY_LAST_SALES_LIST</li>
|
||||
* </ul>
|
||||
* @see IMessageHandler
|
||||
* @version 1.0
|
||||
* @author Guillaume "Cess" Prost
|
||||
*/
|
||||
public class SimpleMessageHandler implements IMessageHandler {
|
||||
|
||||
private final String header_ref_query = "Detail_refus";
|
||||
private final String header_err_query = "Detail_erreur";
|
||||
|
||||
/*
|
||||
* Liste des services du serveur
|
||||
*/
|
||||
private AccountService accountService;
|
||||
private SaleService saleService;
|
||||
private StatisticsService statisticsService;
|
||||
private StockService stockService;
|
||||
//TODO faire qqch de clientService
|
||||
//private ClientService clientService;
|
||||
private ServerStateService serverStateService;
|
||||
private IMessageToolFactory messageToolFactory;
|
||||
|
||||
private MessageManager manager;
|
||||
|
||||
public SimpleMessageHandler() {
|
||||
serverStateService = new ServerStateService();
|
||||
accountService = new AccountService();
|
||||
//clientService = new ClientService();
|
||||
stockService = new StockService();
|
||||
saleService = new SaleService(stockService);
|
||||
statisticsService = new StatisticsService(stockService, saleService);
|
||||
messageToolFactory = new GSonMessageToolFactory();
|
||||
|
||||
manager = new MessageManager();
|
||||
|
||||
/*
|
||||
WAITER-restricted queries
|
||||
*/
|
||||
manager.supportForConnectedAccounts(MessageType.QUERY_STOCK, (message)->{
|
||||
Map<String, String> fields = new HashMap<>();
|
||||
fields.put("stock", messageToolFactory.getListFormatter(Product.class).format(stockService.getProductList()));
|
||||
return new Message(MessageType.MSG_STOCK, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, fields);
|
||||
}, AccountLevel.WAITER);
|
||||
manager.supportForConnectedAccounts(MessageType.QUERY_SALE, (message)->{
|
||||
Map<String, String> fields = new HashMap<>();
|
||||
try {
|
||||
long saleId = saleService.submitSale(messageToolFactory.getObjectParser(Sale.class).parse(message.getField("sale")));
|
||||
fields.put("saleId", Long.toString(saleId));
|
||||
return new Message(MessageType.ACK_SALE, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, fields);
|
||||
}catch(NullPointerException e){
|
||||
fields.put(header_ref_query, e.toString());
|
||||
return new Message(MessageType.REFUSED_QUERY, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, fields);
|
||||
}
|
||||
}, AccountLevel.WAITER);
|
||||
manager.supportForConnectedAccounts(MessageType.QUERY_STAT, (message)->{
|
||||
Map<String, String> fields = new HashMap<>();
|
||||
fields.put("total_sale_worth", Double.toString(statisticsService.getTotalSaleWorth()));
|
||||
fields.put("total_sale_amount", Integer.toString(statisticsService.getTotalAmountSale()));
|
||||
fields.put("total_money_made", Double.toString(statisticsService.getTotalMoneyMade()));
|
||||
fields.put("top_popular_products", messageToolFactory.getListFormatter(LightweightProduct.class).format(statisticsService.getTopPopularProducts(5)));
|
||||
fields.put("staff_sale_worth",Double.toString(statisticsService.getStaffSaleWorth()));
|
||||
fields.put("staff_sale_amount",Integer.toString(statisticsService.getStaffSaleAmount()));
|
||||
fields.put("guest_sale_worth",Double.toString(statisticsService.getGuestSaleWorth()));
|
||||
fields.put("guest_sale_amount",Integer.toString(statisticsService.getGuestSaleAmount()));
|
||||
|
||||
return new Message(MessageType.MSG_STAT, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, fields);
|
||||
}, AccountLevel.WAITER);
|
||||
|
||||
/*
|
||||
MASTER-restricted queries
|
||||
*/
|
||||
manager.supportForConnectedAccounts(MessageType.QUERY_UPDATE, (message)->{
|
||||
try{
|
||||
List<ProductUpdate> updates = messageToolFactory.getListParser(ProductUpdate.class).parse(message.getField("updates"));
|
||||
stockService.applyUpdateList(updates);
|
||||
return new Message(MessageType.ACK_UPDATE, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, null);
|
||||
}catch (ServerQueryException | NullPointerException e){
|
||||
Map<String, String> fields = new HashMap<>();
|
||||
fields.put(header_err_query, e.toString());
|
||||
return new Message(MessageType.ERROR_QUERY, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, fields);
|
||||
}
|
||||
}, AccountLevel.MASTER);
|
||||
|
||||
/*
|
||||
Queries without account connection requirements
|
||||
*/
|
||||
manager.support(MessageType.QUERY_ACCOUNT_LIST, (message)->{
|
||||
Map<String, String> fields = new HashMap<>();
|
||||
fields.put("accounts", messageToolFactory.getListFormatter(Account.class).format(accountService.getAccountList()));
|
||||
return new Message(MessageType.MSG_ACCOUNT_LIST, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, fields);
|
||||
}, AccountLevel.getLowest(), false);
|
||||
manager.support(MessageType.QUERY_CONNECT_ACCOUNT, (message)->{
|
||||
final String desiredStateFieldHeader = "desired_state",
|
||||
accountCredentialsFieldHeader = "account";
|
||||
|
||||
if(message.getFields().containsKey(desiredStateFieldHeader)){
|
||||
if(message.getFields().containsKey(accountCredentialsFieldHeader)){
|
||||
boolean desiredState = messageToolFactory.getObjectParser(Boolean.class).parse(message.getField(desiredStateFieldHeader));
|
||||
Account accountCredentials = messageToolFactory.getObjectParser(Account.class).parse(message.getField(accountCredentialsFieldHeader));
|
||||
|
||||
if(accountService.submitAccountCredentials(accountCredentials, desiredState)){
|
||||
return new Message(MessageType.ACK_CONNECT_ACCOUNT, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, null);
|
||||
}else{
|
||||
Map<String, String> fields = new HashMap<>();
|
||||
fields.put(header_ref_query, "Impossible d'effectuer l'action : identifiants invalides ou état désiré déjà atteint");
|
||||
|
||||
return new Message(MessageType.REFUSED_QUERY, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, fields);
|
||||
}
|
||||
}else{
|
||||
return getMissingArgumentQueryReplyMessage(message, accountCredentialsFieldHeader);
|
||||
}
|
||||
}else{
|
||||
return getMissingArgumentQueryReplyMessage(message, desiredStateFieldHeader);
|
||||
}
|
||||
}, AccountLevel.getLowest(), false);
|
||||
manager.support(MessageType.QUERY_PING, (message)->new Message(MessageType.ACK_PING, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, null), AccountLevel.getLowest(), false);
|
||||
manager.support(MessageType.QUERY_CONFIG_LIST, (message)->{
|
||||
Map<String, String> fields = new HashMap<>();
|
||||
fields.put("config", messageToolFactory.getObjectFormatter(ServerConfig.class).format(serverStateService.getConfig()));
|
||||
return new Message(MessageType.MSG_CONFIG_LIST, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, fields);
|
||||
}, AccountLevel.getLowest(), false);
|
||||
}
|
||||
|
||||
private Message getUnsupportedQueryReplyMessage(Message message){
|
||||
final String msg_ref_unsupported_query = "Ce type de requêtes n'est actuellement pas supportée par ce serveur.";
|
||||
Map<String, String> fields = new HashMap<>();
|
||||
fields.put(header_ref_query, msg_ref_unsupported_query);
|
||||
|
||||
return new Message(MessageType.REFUSED_QUERY,
|
||||
serverStateService.getServer(),
|
||||
message.getEmitter(),
|
||||
message.getUser(),
|
||||
message,
|
||||
fields);
|
||||
}
|
||||
|
||||
private Message getMissingArgumentQueryReplyMessage(Message message, String missingArgumentHeader){
|
||||
Map<String, String> fields = new HashMap<>();
|
||||
fields.put(header_err_query, "The following required header is missing : "+missingArgumentHeader);
|
||||
|
||||
return new Message(MessageType.ERROR_QUERY,
|
||||
serverStateService.getServer(),
|
||||
message.getEmitter(),
|
||||
message.getUser(),
|
||||
message,
|
||||
fields);
|
||||
}
|
||||
|
||||
private Message getExceptionOccuredQueryReplyMessage(Message message, Exception exception){
|
||||
Map<String, String> fields = new HashMap<>();
|
||||
fields.put(header_err_query, exception.getMessage());
|
||||
|
||||
return new Message(MessageType.ERROR_QUERY,
|
||||
serverStateService.getServer(),
|
||||
message.getEmitter(),
|
||||
message.getUser(),
|
||||
message,
|
||||
fields);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message handleMessage(Message message) {
|
||||
|
||||
Map<String, String> fields = new HashMap<>();
|
||||
|
||||
if(manager.contains(message.getType())){
|
||||
if(manager.isQueryRestrictedToConnectedAccount(message.getType())) {
|
||||
if (!isAccountRegisteredAndConnected(message)) {
|
||||
fields.put(header_ref_query, "Compte utilisateur inconnu ou déconnecté.");
|
||||
return new Message(MessageType.REFUSED_QUERY, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, fields);
|
||||
}
|
||||
if (!checkAccountPermission(message)) {
|
||||
fields.put(header_ref_query, "Compte utilisateur avec permission trop faible");
|
||||
return new Message(MessageType.REFUSED_QUERY, serverStateService.getServer(), message.getEmitter(), message.getUser(), message, fields);
|
||||
}
|
||||
}
|
||||
try{
|
||||
return manager.getProcess(message.getType()).execute(message);
|
||||
}catch(Exception e){
|
||||
return getExceptionOccuredQueryReplyMessage(message, e);
|
||||
}
|
||||
}
|
||||
|
||||
return getUnsupportedQueryReplyMessage(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface interne utilisé pour encapsuler le traitement des messages dans un objet
|
||||
*/
|
||||
private interface IMessageProcess{
|
||||
Message execute(Message request);
|
||||
}
|
||||
|
||||
private class MessageTypeEntry{
|
||||
private MessageType type;
|
||||
private IMessageProcess process;
|
||||
private AccountLevel level;
|
||||
private boolean connectedAccountRestriction;
|
||||
|
||||
MessageTypeEntry(MessageType type, IMessageProcess process, AccountLevel level, boolean connectedAccountRestriction) {
|
||||
this.type = type;
|
||||
this.process = process;
|
||||
this.level = level;
|
||||
this.connectedAccountRestriction = connectedAccountRestriction;
|
||||
}
|
||||
|
||||
IMessageProcess getProcess() {
|
||||
return process;
|
||||
}
|
||||
|
||||
AccountLevel getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
boolean isConnectedAccountRestriction() {
|
||||
return connectedAccountRestriction;
|
||||
}
|
||||
|
||||
boolean matches(MessageType type){
|
||||
return type.equals(this.type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
MessageTypeEntry that = (MessageTypeEntry) o;
|
||||
|
||||
return connectedAccountRestriction == that.connectedAccountRestriction && type == that.type && process.equals(that.process) && level == that.level;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = type.hashCode();
|
||||
result = 31 * result + process.hashCode();
|
||||
result = 31 * result + level.hashCode();
|
||||
result = 31 * result + (connectedAccountRestriction ? 1 : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private class MessageManager{
|
||||
private Set<MessageTypeEntry> entries;
|
||||
|
||||
MessageManager(){
|
||||
entries = new HashSet<>();
|
||||
}
|
||||
|
||||
void supportForConnectedAccounts(MessageType type, IMessageProcess process, AccountLevel permissionLevel){
|
||||
support(type, process, permissionLevel, true);
|
||||
}
|
||||
|
||||
void support(MessageType type, IMessageProcess process, AccountLevel permissionLevel, boolean accountConnectionRequired){
|
||||
entries.add(new MessageTypeEntry(type, process, permissionLevel, accountConnectionRequired));
|
||||
}
|
||||
|
||||
private MessageTypeEntry getFirstMatch(MessageType type){
|
||||
return entries.stream().filter(entry->entry.matches(type)).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
IMessageProcess getProcess(MessageType messageType){
|
||||
MessageTypeEntry entry = getFirstMatch(messageType);
|
||||
if(entry!=null)
|
||||
return entry.getProcess();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
AccountLevel getLevel(MessageType messageType){
|
||||
MessageTypeEntry entry = getFirstMatch(messageType);
|
||||
if(entry!=null)
|
||||
return entry.getLevel();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean contains(MessageType type) {
|
||||
return getFirstMatch(type)!=null;
|
||||
}
|
||||
|
||||
boolean isQueryRestrictedToConnectedAccount(MessageType type) {
|
||||
MessageTypeEntry entry = getFirstMatch(type);
|
||||
return entry != null && entry.isConnectedAccountRestriction();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si le compte utilisé pour émettre la query contenu dans le message est enregistré et connecté.
|
||||
* @param message message dont l'expéditeur doit être vérifié
|
||||
* @return true si le compte est existant et connecté, false si au moins une des conditions est fausse.
|
||||
*/
|
||||
private boolean isAccountRegisteredAndConnected(Message message){
|
||||
return accountService.isAccountRegistered(message.getUser()) && accountService.isAccountConnected(message.getUser());
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si le compte émetteur de la query a les autorisations suffisantes pour effectuer la query.
|
||||
* <p/>
|
||||
* @param message message dont le niveau de permission de l'expéditeur doit être vérifié
|
||||
* @return true si l'expéditeur correspond à un compte et qu'il a les autorisations suffisantes, false sinon.
|
||||
*/
|
||||
private boolean checkAccountPermission(Message message){
|
||||
return accountService.isAccountRegistered(message.getUser()) && accountService.getAccountPermissionLevel(message.getUser()).compareTo(manager.getLevel(message.getType()))>=0;
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package com.pqt.server.exception;
|
||||
|
||||
public class ServerQueryException extends Exception {
|
||||
|
||||
public ServerQueryException() {
|
||||
}
|
||||
|
||||
public ServerQueryException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ServerQueryException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public ServerQueryException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
package com.pqt.server.module.account;
|
||||
|
||||
import com.pqt.core.entities.user_account.AccountLevel;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class AccountEntry implements Serializable{
|
||||
private String username, passwordHash, salt;
|
||||
private AccountLevel level;
|
||||
|
||||
public AccountEntry() {
|
||||
}
|
||||
|
||||
public AccountEntry(String username, String passwordHash, String salt, AccountLevel level) {
|
||||
this.username = username;
|
||||
this.passwordHash = passwordHash;
|
||||
this.salt = salt;
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
String getPasswordHash() {
|
||||
return passwordHash;
|
||||
}
|
||||
|
||||
String getSalt() {
|
||||
return salt;
|
||||
}
|
||||
|
||||
AccountLevel getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
AccountEntry that = (AccountEntry) o;
|
||||
|
||||
if (!username.equals(that.username)) return false;
|
||||
if (!passwordHash.equals(that.passwordHash)) return false;
|
||||
if (!salt.equals(that.salt)) return false;
|
||||
return level == that.level;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = username.hashCode();
|
||||
result = 31 * result + passwordHash.hashCode();
|
||||
result = 31 * result + salt.hashCode();
|
||||
result = 31 * result + level.hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
package com.pqt.server.module.account;
|
||||
|
||||
import com.pqt.core.entities.user_account.Account;
|
||||
import com.pqt.core.entities.user_account.AccountLevel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
//TODO ajouter logs
|
||||
/**
|
||||
* Cette classe correspond au service de gestion des comptes utilisateurs. Il permet la vérification de l'existance
|
||||
* d'un compte, de son état (connecté/déconnecté), de changer son état ainsi que de récupérer son niveau d'accréditation.
|
||||
*
|
||||
* @author Guillaume "Cess" Prost
|
||||
* @see AccountLevel
|
||||
* @see Account
|
||||
*/
|
||||
public class AccountService {
|
||||
|
||||
private IAccountDao dao;
|
||||
|
||||
public AccountService() {
|
||||
dao = new FileAccountDao();
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si un compte utilisateur donné est actuellement connecté. Le compte utilisateur doit être existant
|
||||
* pour être connecté.
|
||||
* <p/>
|
||||
* Les informations contenues dans l'objet {@code account} passé en paramètre ne sont pas directements utilisées,
|
||||
* elles servent juste à déterminer le compte utilisateur réel correspondant. <b>Il est nécessaire de s'assurer
|
||||
* que les données passées en paramètres soient justes avant de faire appel à cette méthode.</b>
|
||||
* <p/>
|
||||
* Pour cette méthode, seul le nom d'utilisateur est pris en compte pour établir une correspondance.
|
||||
* <p/>
|
||||
* Dans le cas où aucune correspondance ne peut être faite entre les informations fournies et les données
|
||||
* enregistrées, la valeur {@code false} sera renvoyée.
|
||||
*
|
||||
* @param account Objet {@link Account} dont les informations seront utilisées pour déterminer le compte concerné.
|
||||
*
|
||||
* @return {@code true} si une correspondance a pû être établie et que le compte correspondant est connecté,
|
||||
* {@code false} sinon.
|
||||
*/
|
||||
public boolean isAccountConnected(Account account) {
|
||||
return dao.isAccountConnected(account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Soumet une demande de changement d'état pour un compte utilisateur. Les états possibles sont "connecté" ({@code true})
|
||||
* et "déconnecté" ({@code false}).
|
||||
* <p/>
|
||||
* Les informations contenues dans l'objet {@code account} passé en paramètre ne sont pas directements utilisées,
|
||||
* elles servent juste à déterminer le compte utilisateur réel correspondant. <b>Il est nécessaire de s'assurer
|
||||
* que les données passées en paramètres soient justes avant de faire appel à cette méthode.</b>
|
||||
* <p/>
|
||||
* Pour cette méthode, seul le nom d'utilisateur est pris en compte pour établir une correspondance. Le mot de passe
|
||||
* est uniquement requis pour une connexion, et pas pour une déconnexion.
|
||||
* <p/>
|
||||
* Une fois la correspondance effectuée, une tentative de changement d'état sera faite pour le compte correspondant.
|
||||
* Cette tentative peut échouer si :
|
||||
* <ul>
|
||||
* <li>Le compte est déjà dans l'état désiré</li>
|
||||
* <li>Le mot de passe ne correspond pas (uniquement pour une connexion)</li>
|
||||
* </ul>
|
||||
* Dans le cas d'un échec, l'état du compte reste inchangé et la valeur booléenne {@code false} est renvoyée,
|
||||
* sans plus de détails. Si le changement d'état a eu lieu, la valeur booléenne {@code true} est renvoyée.
|
||||
*
|
||||
* @param account Objet {@link Account} dont les informations seront utilisées pour déterminer le compte concerné.
|
||||
* @param desiredState L'état dans lequel le compte doit se trouver une fois le changement fait.
|
||||
* @return {@code true} si le changement d'état a eu lieu, {@code false} sinon.
|
||||
*/
|
||||
public boolean submitAccountCredentials(Account account, boolean desiredState) {
|
||||
return dao.submitAccountCredentials(account, desiredState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si un compte utilisateur donné existe dans la base de donnée du serveur.
|
||||
* <p/>
|
||||
* Les informations contenues dans l'objet {@code account} passé en paramètre ne sont pas directements utilisées,
|
||||
* elles servent juste à déterminer le compte utilisateur réel correspondant. <b>Il est nécessaire de s'assurer
|
||||
* que les données passées en paramètres soient justes avant de faire appel à cette méthode.</b>
|
||||
* <p/>
|
||||
* Pour cette méthode, seul le nom d'utilisateur est pris en compte pour établir une correspondance.
|
||||
*
|
||||
* @param account Objet {@link Account} dont les informations seront utilisées pour déterminer le compte concerné.
|
||||
* @return {@code true} si une correspondance a pû être établie entre les données fournies et un compte dans la base
|
||||
* de données, {@code false} sinon.
|
||||
*/
|
||||
public boolean isAccountRegistered(Account account){
|
||||
return dao.isAccountRegistered(account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère le niveau de permission du compte utilisateur correspondant aux informations fournies en paramètre.
|
||||
* <p/>
|
||||
* Les informations contenues dans l'objet {@code account} passé en paramètre ne sont pas directements utilisées,
|
||||
* elles servent juste à déterminer le compte utilisateur réel correspondant. <b>Il est nécessaire de s'assurer
|
||||
* que les données passées en paramètres soient justes avant de faire appel à cette méthode.</b>
|
||||
* <p/>
|
||||
* Pour cette méthode, seul le nom d'utilisateur est pris en compte pour établir une correspondance.
|
||||
*
|
||||
* @param account Objet {@link Account} dont les informations seront utilisées pour déterminer le compte concerné.
|
||||
* @return Le niveau de permission {@link AccountLevel} du compte correspondant aux informations si une correspondance
|
||||
* a pû être établie entre {@code account} et un compte utilisateur de la base de donnée, et {@code null} si aucune
|
||||
* correspondance n'a pû être faite.
|
||||
*/
|
||||
public AccountLevel getAccountPermissionLevel(Account account){
|
||||
return dao.getAccountPermissionLevel(account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie la liste des comptes utilisateurs contenus dans la base de données sous forme d'une liste d'objets
|
||||
* {@link Account}. <b>Seuls les noms d'utilisateurs ainsi que les niveaux de permissions sont récupérés, les
|
||||
* autres champs étant volontairement initialisés avec une valeur {@code null}.</b>
|
||||
* @return Une liste d'objet {@link Account} représentant les différents comptes utilisateurs existant dans la base
|
||||
* de données.
|
||||
*/
|
||||
public List<Account> getAccountList(){
|
||||
return dao.getAccountList();
|
||||
}
|
||||
}
|
@ -0,0 +1,148 @@
|
||||
package com.pqt.server.module.account;
|
||||
|
||||
import com.pqt.core.entities.user_account.Account;
|
||||
import com.pqt.core.entities.user_account.AccountLevel;
|
||||
import com.pqt.server.tools.io.ISerialFileManager;
|
||||
import com.pqt.server.tools.io.SimpleSerialFileManagerFactory;
|
||||
import com.pqt.server.tools.security.IHashTool;
|
||||
import com.pqt.server.tools.security.MD5HashTool;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
//TODO ajouter logs
|
||||
|
||||
/**
|
||||
* Implémentation de l'interface {@link IAccountDao} utilisant un fichier contenant des objets sérialisés comme
|
||||
* source de persistance de données.
|
||||
* <p/>
|
||||
* Cette classe n'est pas faite pour gérer les accès concurentiels au fichier assurant la persistance, et n'est donc pas
|
||||
* thread-safe. Elle est conçue pour que tous les accès soient effectués depuis un même thread et depuis un unique objet.
|
||||
* <p/>
|
||||
* Cette classe manipule les mot de passe sous forme chiffrée via un système de hash (md5) + salt, et ne fait pas
|
||||
* persister les mots de passes non-chiffrées. Les noms d'utilisateurs sont stockés sans chiffrage.
|
||||
*/
|
||||
class FileAccountDao implements IAccountDao {
|
||||
|
||||
private static final String ACCOUNT_FILE_NAME = "acc.pqt";
|
||||
|
||||
private Set<AccountEntry> accountEntries;
|
||||
private Set<AccountEntry> connectedAccount;
|
||||
private IHashTool hashTool;
|
||||
private ISerialFileManager<AccountEntry> fileManager;
|
||||
|
||||
FileAccountDao() {
|
||||
accountEntries = new HashSet<>();
|
||||
connectedAccount = new HashSet<>();
|
||||
hashTool = new MD5HashTool();
|
||||
fileManager = SimpleSerialFileManagerFactory.getFileManager(AccountEntry.class, ACCOUNT_FILE_NAME);
|
||||
loadFromFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recherche une correspondance entre un objet {@link Account} et les objets {@link AccountEntry} contenu dans
|
||||
* la collection {@code entries}. La correspondance se base sur la valeur renvoyée par {@link Account#getUsername()}
|
||||
* et sur {@link AccountEntry#getUsername()}.
|
||||
* @param account données à utiliser pour rechercher la correspondance.
|
||||
* @param entries collection de données à utiliser pour rechercher la correspondance
|
||||
* @return La première correspondance trouvée, ou {@code null} si aucune correspondance n'a pu être faite.
|
||||
*/
|
||||
private AccountEntry lookupMatchingEntry(Account account, Collection<AccountEntry> entries){
|
||||
return entries.stream().filter(accountEntry -> accountEntry.getUsername().equals(account.getUsername())).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean isAccountConnected(Account account) {
|
||||
return lookupMatchingEntry(account, connectedAccount)!=null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean submitAccountCredentials(Account account, boolean desiredState) {
|
||||
if(isAccountRegistered(account)){
|
||||
if(desiredState!=isAccountConnected(account)){
|
||||
if(desiredState)
|
||||
return connect(account);
|
||||
else
|
||||
return disconnect(account);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Passe un compte déconnecté dans l'état connecté. N'effecctue le changement que si un compte déconnecté correspond
|
||||
* aux données fournies et que le mot de passe fournit par {@code account.getPassword()} corresspond à celui du
|
||||
* compte correspondant.
|
||||
*
|
||||
* @param account données à utiliser pour effectuer la correspondance et l'identification
|
||||
* @return {@code true} si le changement d'état a eu lieu, {@code false sinon}
|
||||
*/
|
||||
private boolean connect(Account account){
|
||||
AccountEntry entry = lookupMatchingEntry(account, accountEntries);
|
||||
if(entry==null)
|
||||
return false;
|
||||
else{
|
||||
String expectedUsername = entry.getUsername();
|
||||
String expectedPasswordHash = entry.getPasswordHash();
|
||||
String salt = entry.getSalt();
|
||||
|
||||
if(expectedUsername.equals(account.getUsername()) && hashTool.hashAndSalt(account.getPassword(), salt).equals(expectedPasswordHash)){
|
||||
connectedAccount.add(entry);
|
||||
return true;
|
||||
}else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Passe un comtpe connecté dans l'état déconnecté. N'effectue le changement que si un compte connecté correspond
|
||||
* aux données fournies.
|
||||
* @param account données à utiliser pour efffectuer la correspondance avec un compte
|
||||
* @return {@code true} si le changement d'état a eu lieu, {@code false sinon}
|
||||
*/
|
||||
private boolean disconnect(Account account){
|
||||
AccountEntry entry = lookupMatchingEntry(account, accountEntries);
|
||||
if(entry!=null && connectedAccount.contains(entry)){
|
||||
connectedAccount.remove(entry);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean isAccountRegistered(Account account) {
|
||||
return lookupMatchingEntry(account, accountEntries)!=null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized AccountLevel getAccountPermissionLevel(Account account) {
|
||||
if(isAccountRegistered(account))
|
||||
return lookupMatchingEntry(account, accountEntries).getLevel();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized List<Account> getAccountList() {
|
||||
return accountEntries.stream().map(accountEntry -> new Account(accountEntry.getUsername(), null, accountEntry.getLevel())).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sauvegarde les données des comptes dans le fichier de sauvegarde.
|
||||
*/
|
||||
private void saveToFile(){
|
||||
fileManager.saveSetToFile(accountEntries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Charge les données des comptes depuis le fichier de sauvegarde.
|
||||
* <p/>
|
||||
* <b>Attention : pour des raisons de cohérence des données, tous les comptes connectés sont repassés dans l'état
|
||||
* déconnectés une fois le chargement fait.</b>
|
||||
*/
|
||||
private void loadFromFile(){
|
||||
this.accountEntries = new HashSet<>(fileManager.loadSetFromFile());
|
||||
//TODO faire check des comptes au lieu de tout déconnecter?
|
||||
this.connectedAccount.clear();
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package com.pqt.server.module.account;
|
||||
|
||||
import com.pqt.core.entities.user_account.Account;
|
||||
import com.pqt.core.entities.user_account.AccountLevel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Cette interface définit les méthodes de base que doivent avoir les classes de DAO du service {@link AccountService}.
|
||||
* <p/>
|
||||
* Les implémentations de cette interface sont censé assurer la persistance des donnéess de connexions des comptes
|
||||
* utilisateurs et doit également garder une trace niveau runtime de l'état des comptes (état connecté ou état
|
||||
* déconnecté). Enfin, les implémentations doivent pouvoir déterminer une correspondance entre un nom d'utilisateur et
|
||||
* un compte, et doit pouvoir effectuer les changements d'état sur la base d'un mot de passe non-chiffré fournit grâce
|
||||
* à une instance {@link Account} (voir {@link #submitAccountCredentials(Account, boolean)}).
|
||||
*
|
||||
* @author Guillaume "Cess" Prost
|
||||
*/
|
||||
interface IAccountDao {
|
||||
|
||||
/**
|
||||
* @see AccountService#isAccountConnected(Account)
|
||||
*
|
||||
* @param account données à utiliser
|
||||
* @return {@code true} si les données correspondent à un compte et que ce dernier est connecté, {@code false}
|
||||
* sinon.
|
||||
*/
|
||||
boolean isAccountConnected(Account account);
|
||||
|
||||
/**
|
||||
* @see AccountService#submitAccountCredentials(Account, boolean)
|
||||
*
|
||||
* @param account données à utiliser
|
||||
* @param desiredState état désiré pour le compte
|
||||
* @return {@code true} si les données correspondent à un compte et que le changement d'état a eu lieu,
|
||||
* {@code false} sinon.
|
||||
*/
|
||||
boolean submitAccountCredentials(Account account, boolean desiredState);
|
||||
|
||||
/**
|
||||
* @see AccountService#isAccountRegistered(Account)
|
||||
*
|
||||
* @param account données à utiliser
|
||||
* @return {@code true} si les données correspondent à un compte, {@code false} sinon.
|
||||
*/
|
||||
boolean isAccountRegistered(Account account);
|
||||
|
||||
/**
|
||||
* @see AccountService#getAccountPermissionLevel(Account)
|
||||
* @param account données à utiliser
|
||||
* @return Le niveau d'accréditation du compte utilisateur correspondant aux données, ou {@code null} si aucun
|
||||
* compte ne correspond.
|
||||
*/
|
||||
AccountLevel getAccountPermissionLevel(Account account);
|
||||
|
||||
/**
|
||||
* @see AccountService#getAccountList()
|
||||
* @return Une liste d'objet {@link Account} représentant les différents comptes utilisateurs existant dans la base
|
||||
* de données.
|
||||
*/
|
||||
List<Account> getAccountList();
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
package com.pqt.server.module.client;
|
||||
|
||||
import com.pqt.core.entities.members.Client;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class ClientEntry {
|
||||
private Client client;
|
||||
private Date timestamp;
|
||||
|
||||
public ClientEntry(Client client) {
|
||||
this.client = client;
|
||||
timestamp = new Date();
|
||||
}
|
||||
|
||||
public ClientEntry(Client client, Date timestamp) {
|
||||
this.client = client;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public Client getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public void setClient(Client client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public Date getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(Date timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public boolean check(Client client){
|
||||
return this.client.equals(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
ClientEntry that = (ClientEntry) o;
|
||||
|
||||
if (client != null ? !client.equals(that.client) : that.client != null) return false;
|
||||
return timestamp != null ? timestamp.equals(that.timestamp) : that.timestamp == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = client != null ? client.hashCode() : 0;
|
||||
result = 31 * result + (timestamp != null ? timestamp.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
package com.pqt.server.module.client;
|
||||
|
||||
import com.pqt.core.entities.members.Client;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
//TODO ajouter logs
|
||||
|
||||
/**
|
||||
* Cette classe correspond au service de gestion des clients.
|
||||
* <p/>
|
||||
* Un client est une instance du logiciel de composition des commandes, ce dernier étant la principale entitée capable
|
||||
* d'envoyer des requêtes aux serveurs de données.
|
||||
* <p/>
|
||||
* Ce service est censé permettre la tracabilité des clients se connectant au serveur, en gardant en cache tous les
|
||||
* clients avec des horodateurs représentant la date et l'heure de la dernière requête reçue de chaque client.
|
||||
*/
|
||||
public class ClientService {
|
||||
|
||||
private Set<ClientEntry> clientCache;
|
||||
|
||||
public ClientService(){
|
||||
clientCache = new HashSet<>();
|
||||
}
|
||||
|
||||
private ClientEntry lookupClientEntry(Client client){
|
||||
return clientCache.stream().filter(clientEntry -> clientEntry.check(client)).findFirst().orElse(null);
|
||||
}
|
||||
/**
|
||||
* Vérifie si le client donné est connu.
|
||||
* @param client client à vérifier
|
||||
* @return {@code true} si le client donné correspond à une entrée du cache, {@code false} sinon.
|
||||
*/
|
||||
public boolean isClientRegistered(Client client) {
|
||||
return clientCache.contains(client);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre un client dans le cache du service. Si le client existe déjà dans la base, rafraichit l'horodateur
|
||||
* associé.
|
||||
* @param client client à enregistrer
|
||||
*/
|
||||
public void registerClient(Client client) {
|
||||
if(lookupClientEntry(client)==null){
|
||||
clientCache.add(new ClientEntry(client));
|
||||
}else{
|
||||
refreshClientTimestamp(client);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Etabit une correspondance entre {@code client} et une entrée du cache du service, et renvoie l'horodateur associé
|
||||
* à la correspondance. Renvoie {@code null} si aucune correspondance n'a pû être faite.
|
||||
* @param client données à utiliser pour établir la correspondance
|
||||
* @return l'horodateur associé à la correspondance, ou {@code null} si aucune correspondance ne peut être faite.
|
||||
*/
|
||||
public Date getClientTimestamp(Client client) {
|
||||
ClientEntry entry = lookupClientEntry(client);
|
||||
return entry!=null? entry.getTimestamp() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère la liste des clients actuellement dans le cache du service
|
||||
* @return Liste des clients dans le cache
|
||||
*/
|
||||
public List<Client> getClientList(){
|
||||
return clientCache.stream().map(ClientEntry::getClient).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Vide le cache du service.
|
||||
*/
|
||||
public void clear(){
|
||||
clientCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour l'horodateur associé au client donné.
|
||||
* @param client données à utiliser pour établir la correspondance
|
||||
*/
|
||||
private void refreshClientTimestamp(Client client) {
|
||||
ClientEntry entry = lookupClientEntry(client);
|
||||
if(entry!=null){
|
||||
entry.setTimestamp(new Date());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package com.pqt.server.module.sale;
|
||||
|
||||
import com.pqt.core.entities.sale.Sale;
|
||||
|
||||
/**
|
||||
* Interface définissant les méthodes requises pour tout DAO du service de gestion des commandes {@link SaleService}.
|
||||
* <p/>
|
||||
* Les implémentations de cette interface doivent pouvoir valider des commandes, agir sur le stock et générer les
|
||||
* identifiants de commandes validées.
|
||||
* <p/>
|
||||
* Les implémentations peuvent (optionnel) assurer une persistance des données relatives aux commandes validées, et
|
||||
* peuvent donc assurer le revert des commandes {@link #submitSaleRevert(long)}. <b>Le support de cette fonctionnalité
|
||||
* est optionnel</b>.
|
||||
*
|
||||
* @see SaleService pour de plus amples détails sur le fonctionnement attendu des méthodes
|
||||
*/
|
||||
public interface ISaleDao {
|
||||
|
||||
long submitSale(Sale sale);
|
||||
|
||||
boolean isSaleRevertSupported();
|
||||
boolean submitSaleRevert(long id);
|
||||
}
|
@ -0,0 +1,150 @@
|
||||
package com.pqt.server.module.sale;
|
||||
|
||||
import com.pqt.core.entities.product.Product;
|
||||
import com.pqt.core.entities.sale.Sale;
|
||||
import com.pqt.server.module.stock.StockService;
|
||||
import com.pqt.server.tools.FileUtil;
|
||||
import com.pqt.server.tools.entities.SaleContent;
|
||||
import org.apache.commons.io.input.ReversedLinesFileReader;
|
||||
|
||||
import java.io.*;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Implémentation de l'interface {@link ISaleDao} utilisant un fichier comme moyen pour assurer la persistance des
|
||||
* données relatives aux commandes validées. <b>Cette implémentation ne supporte pas le rollback de commandes</b>.
|
||||
* <p/>.
|
||||
* La persistance des données est faite de sorte que ces données soient lisibles par un humain lorsque le fichier est
|
||||
* ouvert avecc un éditeur de texte quelconque.
|
||||
* <p/>
|
||||
* Les identifiants attribués aux commandes validées sont incrémentés à chaque fois, en se basant soit sur la dernière
|
||||
* valeur attribuée (lue depuis le fichier de sauvegarde lors de l'instantiation), soit sur une valeur par défaut. Ils
|
||||
* sont tous <b>positifs et non-nuls</b>.
|
||||
*/
|
||||
public class NoRevertFileSaleDao implements ISaleDao {
|
||||
|
||||
private static final String SALE_LOG_FILE_NAME = "sale_log.txt";
|
||||
private static final long DEFAULT_SALE_ID = 0; //équivaut à la valeur du premier id - 1
|
||||
private StockService stockService;
|
||||
private long nextSaleId;
|
||||
private ISaleRenderer renderer;
|
||||
|
||||
NoRevertFileSaleDao(StockService stockService) {
|
||||
this.stockService = stockService;
|
||||
this.renderer = getRenderer();
|
||||
nextSaleId = readLastSaleIdFromFile()+1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long submitSale(Sale sale) {
|
||||
boolean valid = true;
|
||||
Iterator<Product> it = sale.getProducts().keySet().iterator();
|
||||
while(valid && it.hasNext()){
|
||||
Product p = it.next();
|
||||
Product product = stockService.getProduct(p.getId());
|
||||
valid = product!=null
|
||||
&& p.equals(product)
|
||||
&& product.isSellable()
|
||||
&& product.getAmountRemaining()>=sale.getProducts().get(p);
|
||||
}
|
||||
|
||||
if(!valid)
|
||||
return -1;
|
||||
|
||||
long saleId = nextSaleId;
|
||||
stockService.applySale(new SaleContent(sale));
|
||||
logSale(sale, saleId);
|
||||
generateNextSaleId();
|
||||
return saleId;
|
||||
}
|
||||
|
||||
private void generateNextSaleId() {
|
||||
nextSaleId++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the last sale id written in the log file with title {@link #SALE_LOG_FILE_NAME} or a default value if such id has not been found.
|
||||
* <p/>
|
||||
* Different reasons why this method may not find any id :<br/>
|
||||
* - file does not exist<br/>
|
||||
* - file is empty<br/>
|
||||
* - file does not respect the expected syntax for writing sales' data<br/>
|
||||
* <p/>
|
||||
* The log file with title {@link #SALE_LOG_FILE_NAME} is not created by this method if it doesn't exist yet.
|
||||
* @return last sale id used in the log file, or -1 if none was found.
|
||||
*/
|
||||
private long readLastSaleIdFromFile(){
|
||||
long id = DEFAULT_SALE_ID;
|
||||
if(FileUtil.exist(SALE_LOG_FILE_NAME)){
|
||||
try(ReversedLinesFileReader rlfr = new ReversedLinesFileReader(new File("SALE_LOG_FILE_NAME"))){
|
||||
boolean stop = false;
|
||||
do{
|
||||
try {
|
||||
String line = rlfr.readLine();
|
||||
if(line.matches("^[0-9]+$")){
|
||||
id = Long.parseLong(line.substring(1));
|
||||
stop = true;
|
||||
}
|
||||
}catch (EOFException e){
|
||||
stop = true;
|
||||
}
|
||||
}while(!stop);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return id;
|
||||
}else{
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSaleRevertSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean submitSaleRevert(long id) {
|
||||
//TODO Créer un nouveau dao qui supporte le revert
|
||||
throw new UnsupportedOperationException("Le revert de commandes n'est pas supporté");
|
||||
}
|
||||
|
||||
private void logSale(Sale sale, long saleId){
|
||||
try(FileOutputStream fos = new FileOutputStream(SALE_LOG_FILE_NAME);
|
||||
PrintWriter pw = new PrintWriter(fos)){
|
||||
|
||||
pw.append(renderer.render(sale, saleId));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private ISaleRenderer getRenderer(){
|
||||
return(sale, id)->{
|
||||
StringBuffer sb = new StringBuffer("\n#").append(id).append("\n");
|
||||
String separator = "-----";
|
||||
DateFormat dateFormat = new SimpleDateFormat("<dd/ww/yyyy - HH:mm:ss>");
|
||||
|
||||
sb.append("type : ").append(sale.getType().name()).append("\n");
|
||||
sb.append("at : ").append(dateFormat.format(sale.getOrderedAt())).append("\n");
|
||||
if(sale.getOrderedBy()!=null)
|
||||
sb.append("by : ").append(sale.getOrderedBy().getUsername()).append("(").append(sale.getOrderedBy().getPermissionLevel().name()).append(")").append("\n");
|
||||
if(sale.getOrderedFor()!=null)
|
||||
sb.append("for : ").append(sale.getOrderedFor().getUsername()).append("(").append(sale.getOrderedFor().getPermissionLevel().name()).append(")").append("\n");
|
||||
sb.append(separator).append("\n");
|
||||
sb.append("Products : \n");
|
||||
sale.getProducts().keySet().forEach(p->{
|
||||
int productAmount = sale.getProducts().get(p);
|
||||
sb.append(String.format(" * %s (%du, %f€) : %d remaining in stock", p.getName(), productAmount, p.getPrice()*(double)productAmount, p.getAmountRemaining()-productAmount)).append("\n");
|
||||
});
|
||||
sb.append(separator);
|
||||
return sb.toString();
|
||||
};
|
||||
}
|
||||
|
||||
private interface ISaleRenderer{
|
||||
String render(Sale sale, long saleId);
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
package com.pqt.server.module.sale;
|
||||
|
||||
import com.pqt.core.entities.sale.Sale;
|
||||
import com.pqt.server.exception.ServerQueryException;
|
||||
import com.pqt.server.module.sale.listeners.ISaleFirerer;
|
||||
import com.pqt.server.module.sale.listeners.ISaleListener;
|
||||
import com.pqt.server.module.sale.listeners.SimpleSaleFirerer;
|
||||
import com.pqt.server.module.stock.StockService;
|
||||
|
||||
//TODO ajouter logs
|
||||
|
||||
/**
|
||||
* Cette classe correspond au service de validation des commandes de produits.
|
||||
* <p/>
|
||||
* Ce service est censé pouvoir déterminer si une commmande (classe {@link Sale}) est valide ou non, et doit le cas
|
||||
* échéant effectuer les retraits de produits du stock. A chaque commande validée doit correspondre un identifiant
|
||||
* unique, qui doit être renvoyé en réponse de la validation. Cet identifiant doit permettre de pouvoir annuler
|
||||
* ultérieurement la commande correspondante via la méthode {@link #submitSaleRevert(long)}.
|
||||
* <p/>
|
||||
* Une commande est considérée comme valide si tous les produits composants la commande existent dans le stock et que
|
||||
* les quantités demandées dans la commande sont disponibles en stock.
|
||||
* <p/>
|
||||
* Ce service met également à disposition la possibilité d'enregistrer des observateurs, qui seront utilisés pour
|
||||
* exécuter des méthodes lors de certains événements, comme la validation d'une commande.
|
||||
*
|
||||
* @see ISaleListener
|
||||
*/
|
||||
public class SaleService {
|
||||
|
||||
private ISaleDao dao;
|
||||
private ISaleFirerer eventFirerer;
|
||||
|
||||
public SaleService(StockService stockService) {
|
||||
dao = new NoRevertFileSaleDao(stockService);
|
||||
eventFirerer = new SimpleSaleFirerer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Soumet une commande au service pour validation. Si la commande est validée, les stocks seront débités et
|
||||
* l'identifiant de la commande sera renvoyé. Si la commande n'est pas validée, la valeur {@value -1} sera renvoyée
|
||||
* et les stocks resterons inchangés.
|
||||
* @param sale commande à valider
|
||||
* @return l'identifiant positif non-nul attribué à la commande si elle est validée, {@value -1} sinon.
|
||||
*/
|
||||
public long submitSale(Sale sale) {
|
||||
long id = dao.submitSale(sale);
|
||||
if(id!=-1) eventFirerer.fireSaleValidatedEvent(sale);
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Détermine si le rollback de commande est supporté par la configuration actuelle du serveur ou non.
|
||||
* <p/>
|
||||
* Tenter d'effectuer un rollback de commande alors que ce dernier n'est pas supporté lèvera une
|
||||
* {@link UnsupportedOperationException}.
|
||||
*
|
||||
* @return {@code true} si le rollback de commande est supporté, {@code false} sinon.
|
||||
*/
|
||||
public boolean isSaleRevertSupported(){
|
||||
return dao.isSaleRevertSupported();
|
||||
}
|
||||
|
||||
/**
|
||||
* Demande le rollback d'une commande en se basant sur l'identifiant.
|
||||
* @param id identifiant de la commande à annuler
|
||||
* @return {@code true} si la commande a bel et bien été annulée, {@code false} si aucun changement n'a été fait.
|
||||
*/
|
||||
public boolean submitSaleRevert(long id) {
|
||||
if(isSaleRevertSupported())
|
||||
return dao.submitSaleRevert(id);
|
||||
else
|
||||
throw new UnsupportedOperationException("Cette opération ('sale revert') n'est pas supportée par la configuration actuelle du serveur");
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajout un observateur au service, qui sera notifié lorsque certains événements auront lieu.
|
||||
* @param l observateur à ajouter.
|
||||
* @see ISaleListener
|
||||
*/
|
||||
public void addListener(ISaleListener l) {
|
||||
eventFirerer.addListener(l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retire un observateur du service.
|
||||
* @param l observateur à retirer.
|
||||
* @see ISaleListener
|
||||
*/
|
||||
public void removeListener(ISaleListener l){
|
||||
eventFirerer.addListener(l);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.pqt.server.module.sale.listeners;
|
||||
|
||||
import com.pqt.core.entities.sale.Sale;
|
||||
|
||||
public interface ISaleFirerer {
|
||||
|
||||
void addListener(ISaleListener l);
|
||||
void removeListener(ISaleListener l);
|
||||
|
||||
void fireSaleValidatedEvent(Sale sale);
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.pqt.server.module.sale.listeners;
|
||||
|
||||
import com.pqt.core.entities.sale.Sale;
|
||||
|
||||
import java.util.EventListener;
|
||||
|
||||
public interface ISaleListener extends EventListener{
|
||||
void onSaleValidatedEvent(Sale sale);
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package com.pqt.server.module.sale.listeners;
|
||||
|
||||
import com.pqt.core.entities.sale.Sale;
|
||||
|
||||
public class SaleListenerAdapter implements ISaleListener {
|
||||
@Override
|
||||
public void onSaleValidatedEvent(Sale sale) {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package com.pqt.server.module.sale.listeners;
|
||||
|
||||
import com.pqt.core.entities.sale.Sale;
|
||||
|
||||
import javax.swing.event.EventListenerList;
|
||||
|
||||
public class SimpleSaleFirerer implements ISaleFirerer {
|
||||
|
||||
private EventListenerList listeners;
|
||||
|
||||
public SimpleSaleFirerer() {
|
||||
listeners = new EventListenerList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(ISaleListener l) {
|
||||
listeners.add(ISaleListener.class, l);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(ISaleListener l) {
|
||||
listeners.remove(ISaleListener.class, l);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fireSaleValidatedEvent(Sale sale) {
|
||||
for(ISaleListener l : listeners.getListeners(ISaleListener.class)){
|
||||
l.onSaleValidatedEvent(sale);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package com.pqt.server.module.state;
|
||||
|
||||
public class ServerState {
|
||||
|
||||
private int port;
|
||||
|
||||
private boolean serverState;
|
||||
|
||||
public ServerState() {
|
||||
port = -1;
|
||||
serverState = false;
|
||||
}
|
||||
|
||||
public ServerState(int port) {
|
||||
this.port = port;
|
||||
serverState = false;
|
||||
}
|
||||
|
||||
public ServerState(ServerState clone){
|
||||
this.serverState = clone.serverState;
|
||||
this.port = clone.port;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public boolean isServerState() {
|
||||
return serverState;
|
||||
}
|
||||
|
||||
public void setServerState(boolean serverState) {
|
||||
this.serverState = serverState;
|
||||
}
|
||||
|
||||
public ServerState copy() {
|
||||
return new ServerState(this);
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
package com.pqt.server.module.state;
|
||||
|
||||
import com.pqt.core.entities.members.DataServer;
|
||||
import com.pqt.core.entities.server_config.ConfigFields;
|
||||
import com.pqt.core.entities.server_config.ServerConfig;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Cette classe correspond au service interne du serveur, chargé de conserver les données propres au serveur, comme
|
||||
* son adresse IP ou encore les différents aspects de la configuration actuelle. Il permet également de récupérer un
|
||||
* objet {@link DataServer}, implémentation de {@link com.pqt.core.entities.members.PqtMember}, qui sert à représenter
|
||||
* ce serveur dans les messages, soit comme émetteur, soit comme destinataire.
|
||||
*
|
||||
* @see com.pqt.core.entities.messages.Message
|
||||
*
|
||||
* @author Guillaume "Cess" Prost
|
||||
*/
|
||||
public class ServerStateService {
|
||||
|
||||
private ServerState serverState;
|
||||
private DataServer server;
|
||||
private ServerConfig config;
|
||||
|
||||
public ServerStateService() {
|
||||
this.server = new DataServer();
|
||||
this.serverState = new ServerState();
|
||||
this.config = new ServerConfig(
|
||||
ConfigFields.ALLOW_ACCOUNT_CONNECT,
|
||||
ConfigFields.ALLOW_SALE_COMMIT,
|
||||
ConfigFields.ALLOW_STOCK_UPDATE,
|
||||
ConfigFields.ALLOW_STOCK_VIEW);
|
||||
|
||||
//TODO config adresse IP
|
||||
//this.com.pqt.server.setAddress(...);
|
||||
}
|
||||
|
||||
public void startServer() {
|
||||
serverState.setServerState(true);
|
||||
}
|
||||
|
||||
public void stopServer() {
|
||||
serverState.setServerState(false);
|
||||
}
|
||||
|
||||
public void changeConnectionPort(int port) {
|
||||
serverState.setPort(port);
|
||||
}
|
||||
|
||||
public DataServer getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
public void setServer(DataServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public ServerState getServerStateCopy() {
|
||||
return serverState.copy();
|
||||
}
|
||||
|
||||
public ServerConfig getConfig() {
|
||||
return config;
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
package com.pqt.server.module.statistics;
|
||||
|
||||
import com.pqt.core.entities.product.LightweightProduct;
|
||||
import com.pqt.core.entities.product.Product;
|
||||
import com.pqt.core.entities.sale.Sale;
|
||||
import com.pqt.server.module.sale.listeners.ISaleListener;
|
||||
import com.pqt.server.module.sale.listeners.SaleListenerAdapter;
|
||||
import com.pqt.server.module.stock.StockService;
|
||||
import com.pqt.server.module.sale.SaleService;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
//TODO Ajouter logs
|
||||
|
||||
/**
|
||||
* Cette classe correspond au sservices de statistiques du serveur, chargé de calculer et de mettre à disposition
|
||||
* diverses données concernant les ventes effectuées et les produits vendus.
|
||||
*/
|
||||
public class StatisticsService {
|
||||
|
||||
private StockService stockService;
|
||||
|
||||
private int totalSaleAmount, staffSaleAmount, guestSaleAmount;
|
||||
private double totalMoneyMade, totalSaleWorth, staffSaleWorth, guestSaleWorth;
|
||||
|
||||
public StatisticsService(StockService stockService, SaleService saleService) {
|
||||
this.stockService = stockService;
|
||||
|
||||
totalSaleAmount = 0;
|
||||
staffSaleAmount = 0;
|
||||
guestSaleAmount = 0;
|
||||
|
||||
totalMoneyMade = 0;
|
||||
totalSaleWorth = 0;
|
||||
staffSaleWorth = 0;
|
||||
guestSaleWorth = 0;
|
||||
|
||||
saleService.addListener(new SaleListenerAdapter() {
|
||||
@Override
|
||||
public void onSaleValidatedEvent(Sale sale) {
|
||||
double price = sale.getTotalPrice(), worth = sale.getTotalWorth();
|
||||
totalSaleWorth+=worth;
|
||||
totalMoneyMade+=price;
|
||||
totalSaleAmount++;
|
||||
switch (sale.getType()){
|
||||
case OFFERED_GUEST:
|
||||
guestSaleAmount++;
|
||||
guestSaleWorth+=worth;
|
||||
break;
|
||||
case OFFERED_STAFF_MEMBER:
|
||||
staffSaleAmount++;
|
||||
staffSaleWorth+=price;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public int getTotalAmountSale() {
|
||||
return totalSaleAmount;
|
||||
}
|
||||
|
||||
public double getTotalMoneyMade() {
|
||||
return totalMoneyMade;
|
||||
}
|
||||
|
||||
public double getTotalSaleWorth() {
|
||||
return totalSaleWorth;
|
||||
}
|
||||
|
||||
public List<LightweightProduct> getTopPopularProducts(int amount) {
|
||||
return stockService.getProductList().stream()
|
||||
.sorted(Comparator.comparingInt(Product::getAmountSold))
|
||||
.limit(amount)
|
||||
.map(LightweightProduct::new)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public int getStaffSaleAmount() {
|
||||
return staffSaleAmount;
|
||||
}
|
||||
|
||||
public double getStaffSaleWorth() {
|
||||
return staffSaleWorth;
|
||||
}
|
||||
|
||||
public int getGuestSaleAmount() {
|
||||
return guestSaleAmount;
|
||||
}
|
||||
|
||||
public double getGuestSaleWorth() {
|
||||
return guestSaleWorth;
|
||||
}
|
||||
}
|
@ -0,0 +1,173 @@
|
||||
package com.pqt.server.module.stock;
|
||||
|
||||
import com.pqt.core.entities.product.Product;
|
||||
import com.pqt.server.tools.entities.SaleContent;
|
||||
import com.pqt.server.tools.io.ISerialFileManager;
|
||||
import com.pqt.server.tools.io.SimpleSerialFileManagerFactory;
|
||||
|
||||
import java.lang.IllegalStateException;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Implémentation de l'interface {@link IStockDao} utilisant un fichier de sauvegarde pour assurer la persistance des
|
||||
* données liées aux produits vendus.
|
||||
* <p/>
|
||||
* Les données sont écrites et lues dans le fichier grâce au méchanisme de sérialisation/désérialisation. Elles ne sont
|
||||
* pas faites pour être lisibles directement par un humain.
|
||||
*
|
||||
* @author Guillaume "Cess" Prost
|
||||
*/
|
||||
public class FileStockDao implements IStockDao {
|
||||
|
||||
private static final String STOCK_FILE_NAME = "stock.pqt";
|
||||
private ISerialFileManager<Product> fileManager;
|
||||
private long nextProductId;
|
||||
private Random random;
|
||||
|
||||
private Map<Long, Product> products;
|
||||
|
||||
FileStockDao() {
|
||||
random = new Random();
|
||||
fileManager = SimpleSerialFileManagerFactory.getFileManager(Product.class, STOCK_FILE_NAME);
|
||||
loadFromFile();
|
||||
generateNextProductId();
|
||||
}
|
||||
|
||||
private void generateNextProductId() {
|
||||
Long newId;
|
||||
do{
|
||||
newId = random.nextLong();
|
||||
}while (products.containsKey(newId));
|
||||
nextProductId = newId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see com.pqt.server.module.stock.IStockDao#getProductList()
|
||||
*/
|
||||
public List<Product> getProductList() {
|
||||
return copyOfProductList();
|
||||
}
|
||||
|
||||
private List<Product> copyOfProductList() {
|
||||
List<Product> copy = new ArrayList<>();
|
||||
products.values().forEach(p->copy.add(new Product(p)));
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see com.pqt.server.module.stock.IStockDao#getProduct(long)
|
||||
*/
|
||||
public Product getProduct(long id) {
|
||||
return products.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see com.pqt.server.module.stock.IStockDao#addProduct(com.pqt.core.entities.product.Product)
|
||||
*/
|
||||
public long addProduct(Product product) {
|
||||
product.setId(nextProductId);
|
||||
this.products.put(nextProductId, product);
|
||||
long reply = nextProductId;
|
||||
generateNextProductId();
|
||||
saveToFile();
|
||||
return reply;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see com.pqt.server.module.stock.IStockDao#removeProduct(long)
|
||||
*/
|
||||
public void removeProduct(long id) {
|
||||
Product product = getProduct(id);
|
||||
if(product!=null){
|
||||
this.products.remove(product.getId());
|
||||
saveToFile();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see com.pqt.server.module.stock.IStockDao#modifyProduct(long, com.pqt.core.entities.product.Product)
|
||||
*/
|
||||
public void modifyProduct(long id, Product product) {
|
||||
if(this.products.containsKey(id)){
|
||||
product.setId(id);
|
||||
this.products.put(id, product);
|
||||
saveToFile();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applySale(SaleContent saleContent) throws IllegalArgumentException {
|
||||
if(saleContent==null)
|
||||
return;
|
||||
|
||||
try {
|
||||
for(Product product : saleContent.getProductList()){
|
||||
applyRecursiveStockRemoval(product, saleContent.getProductAmount(product));
|
||||
applySoldCounterIncrease(product, saleContent.getProductAmount(product));
|
||||
}
|
||||
saveToFile();
|
||||
}catch (IllegalStateException e){
|
||||
loadFromFile();
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cette méthode augmente le compteur de vente pour un produit donné dans la BDD.
|
||||
*
|
||||
* @param product données à utiliser pour déterminer le produit correspondant dans la BDD dont les données doivent
|
||||
* être manipulées.
|
||||
* @param amount montant à ajouter
|
||||
* @throws IllegalStateException exception levée si le produit donné ne peut pas être trouvé dans la base de donnée.
|
||||
*/
|
||||
private void applySoldCounterIncrease(Product product, Integer amount) throws IllegalStateException{
|
||||
Product correspondingProduct = getProduct(product.getId());
|
||||
if(correspondingProduct!=null){
|
||||
correspondingProduct.setAmountSold(correspondingProduct.getAmountSold() + amount);
|
||||
}else{
|
||||
StringBuilder sb = new StringBuilder("StockService>StockDao : Un produit vendu ne correspond pas à un produit connu : ");
|
||||
sb.append(product.getId()).append(" - ").append(product.getName()).append("(").append(product.getCategory()).append(")");
|
||||
throw new IllegalStateException(sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cette méthode retire à un produit donné de la BDD le montant spécifié (diminue la valeur de
|
||||
* {@link Product#amountRemaining}), puis effectue récursivement la même opération pour tous les composants de ce
|
||||
* produit.
|
||||
*
|
||||
* @param product données à utiliser pour déterminer le produit correspondant dans la BDD dont les données doivent
|
||||
* être manipulées.
|
||||
* @param amount montant à déduire
|
||||
* @throws IllegalStateException exception levée si le produit donné ne peut pas être trouvé dans la base de donnée.
|
||||
*/
|
||||
private void applyRecursiveStockRemoval(Product product, int amount) throws IllegalStateException {
|
||||
Product correspondingProduct = getProduct(product.getId());
|
||||
if(correspondingProduct!=null) {
|
||||
correspondingProduct.setAmountRemaining(correspondingProduct.getAmountRemaining() - amount);
|
||||
correspondingProduct.getComponents().forEach(component -> applyRecursiveStockRemoval(component, amount));
|
||||
}else{
|
||||
StringBuilder sb = new StringBuilder("StockService>StockDao : Un produit vendu ne correspond pas à un produit connu : ");
|
||||
sb.append(product.getId()).append(" - ").append(product.getName()).append("(").append(product.getCategory()).append(")");
|
||||
throw new IllegalStateException(sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cette méthode charge les données relatives aux produits depuis le fichier de sauvegarde. Si ce fichier n'existe
|
||||
* pas, il est créé et la liste des produits est vidée.
|
||||
*/
|
||||
private void loadFromFile() {
|
||||
Map<Long, Product> loadedData = new HashMap<>();
|
||||
fileManager.loadListFromFile().forEach(product -> loadedData.put(product.getId(), product));
|
||||
products = new HashMap<>(loadedData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cette méthode écrit les données relatives aux produits dans le fichier de sauvegarde, écrasant le contenu
|
||||
* précédent.
|
||||
*/
|
||||
private void saveToFile() {
|
||||
fileManager.saveListToFile(new ArrayList<>(products.values()));
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package com.pqt.server.module.stock;
|
||||
|
||||
import com.pqt.core.entities.product.Product;
|
||||
import com.pqt.server.tools.entities.SaleContent;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Interface définissant les méthodes requises pour tout DAO du service de gestion des commandes {@link StockService}.
|
||||
* <p/>
|
||||
* Les implémentations doivent assurer une persistance des données relatives aux produits vendus, et doivent assurer
|
||||
* les modifications et les applications de ventes.
|
||||
*
|
||||
* @see StockService pour de plus amples détails sur le fonctionnement attendu des méthodes
|
||||
*
|
||||
* @author Guillaume "Cess" Prost
|
||||
*/
|
||||
public interface IStockDao {
|
||||
|
||||
/**
|
||||
* Renvoie une copie de la liste des produits contenus dans la base de donnée.
|
||||
* @return copie de la liste des produits.
|
||||
*/
|
||||
List<Product> getProductList();
|
||||
|
||||
/**
|
||||
* Renvoie le produit correspondant à l'identifiant donné.
|
||||
* @param id identifiant du produit à récupérer
|
||||
* @return Produit correspondant, ou {@code null} si aucun produit ne correspond
|
||||
*/
|
||||
Product getProduct(long id);
|
||||
|
||||
/**
|
||||
* Ajoute un produit dans la base de donnée. Son identifiant sera éventuellement modifié pour éviter les conflits.
|
||||
* Dans tous les cas, l'identifiant final du produit est renvoyé une fois l'ajout effectué.
|
||||
* @param product produit à ajouter$
|
||||
* @return identifiant du produit ajouté.
|
||||
*/
|
||||
long addProduct(Product product);
|
||||
|
||||
/**
|
||||
* Supprime le produit correspondant à l'identifiant donné.
|
||||
* @param id identifiant du produit à supprimer.
|
||||
*/
|
||||
void removeProduct(long id);
|
||||
|
||||
|
||||
/**
|
||||
* Modifie le produit correspondant à l'identifiant donné en le remplaçant par {@code product}. L'identifiant
|
||||
* reste inchangé.
|
||||
* <p/>
|
||||
* <b>Si {@code id} ne correspond à aucun produit, aucune modification n'est effectué. Cela signifie que
|
||||
* {@code product} n'est pas ajouté à la BDD.</b>
|
||||
* @param id identifiant du produit à modifier
|
||||
* @param product nouvelle version du produit
|
||||
*/
|
||||
void modifyProduct(long id, Product product);
|
||||
|
||||
/**
|
||||
* Applique les modifications de stocks liées à une commande validée, représenté par {@code saleContent}.
|
||||
* <p/>
|
||||
* @param saleContent détail des produits et quantités de la commande validée.
|
||||
* @throws IllegalArgumentException Exception levée si une erreur liée au contenu de {@code saleContent} survient.
|
||||
*/
|
||||
void applySale(SaleContent saleContent) throws IllegalArgumentException;
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
package com.pqt.server.module.stock;
|
||||
|
||||
import com.pqt.core.entities.product.Product;
|
||||
import com.pqt.core.entities.product.ProductUpdate;
|
||||
import com.pqt.server.exception.ServerQueryException;
|
||||
import com.pqt.server.tools.entities.SaleContent;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
//TODO ajouter logs
|
||||
|
||||
/**
|
||||
* Cette classe correspond au service de gestion du stock de produits. Il est en charge de la persistance des
|
||||
* données liées aux produits, de founir un accès centralisé à ces données et se charge également d'appliquer les
|
||||
* mises à jour de stock (ajout, modif ou suppr de produits) et les ventes de produits issues des commandes
|
||||
* (modification des quantités).
|
||||
* <p/>
|
||||
* <b>Attention : ce service ne se charge pas de valider les commandes, il ne fait que modifier les quantités comme si
|
||||
* la commande avait été validé</b>
|
||||
*
|
||||
* @see Product
|
||||
* @see ProductUpdate
|
||||
* @see SaleContent
|
||||
* @author Guillaume "Cess" Prost
|
||||
*/
|
||||
public class StockService {
|
||||
|
||||
private IStockDao dao;
|
||||
|
||||
public StockService() {
|
||||
dao = new FileStockDao();
|
||||
}
|
||||
|
||||
public List<Product> getProductList() {
|
||||
return dao.getProductList();
|
||||
}
|
||||
|
||||
public Product getProduct(long id) {
|
||||
return dao.getProduct(id);
|
||||
}
|
||||
|
||||
public void applySale(SaleContent saleContent) {
|
||||
dao.applySale(saleContent);
|
||||
}
|
||||
|
||||
public void applyUpdateList(List<ProductUpdate> updates) throws ServerQueryException{
|
||||
for(ProductUpdate upd : updates){
|
||||
if(upd.getOldVersion()==null){
|
||||
addProduct(upd.getNewVersion());
|
||||
}else if(upd.getNewVersion()==null){
|
||||
removeProduct(upd.getOldVersion().getId());
|
||||
}else if(upd.getOldVersion()!=null && upd.getNewVersion()!=null){
|
||||
modifyProduct(upd.getOldVersion().getId(), upd.getNewVersion());
|
||||
}else{
|
||||
throw new ServerQueryException("Object ProductUpdate invalide : old et new vallent tous les deux null");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addProduct(Product product) {
|
||||
dao.addProduct(product);
|
||||
}
|
||||
|
||||
private void removeProduct(long id) {
|
||||
dao.removeProduct(id);
|
||||
}
|
||||
|
||||
private void modifyProduct(long id, Product product) {
|
||||
dao.modifyProduct(id, product);
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package com.pqt.server.servlets;
|
||||
|
||||
import com.pqt.core.communication.GSonMessageToolFactory;
|
||||
import com.pqt.core.communication.IMessageToolFactory;
|
||||
import com.pqt.core.entities.messages.Message;
|
||||
import com.pqt.server.controller.IMessageHandler;
|
||||
import com.pqt.server.controller.SimpleMessageHandler;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.annotation.WebServlet;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
//TODO ajouter logs
|
||||
@WebServlet(name = "QueryServlet", urlPatterns = "/")
|
||||
public class QueryServlet extends HttpServlet {
|
||||
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
||||
executeServletProcess(request, response);
|
||||
}
|
||||
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
||||
executeServletProcess(request, response);
|
||||
}
|
||||
|
||||
private void executeServletProcess(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
||||
IMessageToolFactory messageToolFactory = new GSonMessageToolFactory();
|
||||
IMessageHandler msgHandler = new SimpleMessageHandler();
|
||||
|
||||
if (request.getParameter("message") != null) {
|
||||
Message resp = msgHandler.handleMessage(messageToolFactory.getObjectParser(Message.class).parse(request.getParameter("message")));
|
||||
|
||||
response.getWriter().write(messageToolFactory.getObjectFormatter(Message.class).format(resp));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package com.pqt.server.tools;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public class FileUtil {
|
||||
|
||||
/**
|
||||
* @see #createFileIfNotExist(Path)
|
||||
*/
|
||||
public static boolean createFileIfNotExist(String filePath) throws IOException {
|
||||
return createFileIfNotExist(Paths.get(filePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given file path correspond to an existing file, and create it if it doesn't.
|
||||
*
|
||||
* @param filePath the file path to check
|
||||
*
|
||||
* @return {@code true} if the file has been created, {@code false} if it already existed.
|
||||
* @throws IOException if any IOException happend during this method's execution.
|
||||
*/
|
||||
public static boolean createFileIfNotExist(Path filePath) throws IOException {
|
||||
if(FileUtil.exist(filePath)){
|
||||
Files.createFile(filePath);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean exist(Path path) {
|
||||
return Files.exists(path);
|
||||
}
|
||||
|
||||
public static boolean exist(String path) {
|
||||
return Files.exists(Paths.get(path));
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package com.pqt.server.tools.entities;
|
||||
|
||||
import com.pqt.core.entities.product.Product;
|
||||
import com.pqt.core.entities.sale.Sale;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class SaleContent {
|
||||
private Map<Product, Integer> content;
|
||||
|
||||
public SaleContent() {
|
||||
content = new HashMap<>();
|
||||
}
|
||||
|
||||
public SaleContent(Sale sale){
|
||||
content = new HashMap<>(sale.getProducts());
|
||||
}
|
||||
|
||||
public void addProduct(Product product, Integer amount){
|
||||
if(content.containsKey(product)){
|
||||
content.replace(product, content.get(product)+amount);
|
||||
}else{
|
||||
content.put(product, amount);
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<Product> getProductList(){
|
||||
return content.keySet();
|
||||
}
|
||||
|
||||
public boolean contains(Product product){
|
||||
return content.containsKey(product);
|
||||
}
|
||||
|
||||
public Integer getProductAmount(Product product){
|
||||
if(content.containsKey(product))
|
||||
return content.get(product);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package com.pqt.server.tools.io;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
//TODO écrire javadoc
|
||||
public interface ISerialFileManager<T> {
|
||||
List<T> loadListFromFile();
|
||||
Set<T> loadSetFromFile();
|
||||
void saveListToFile(List<T> list);
|
||||
void saveSetToFile(Set<T> set);
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
package com.pqt.server.tools.io;
|
||||
|
||||
import com.pqt.server.tools.FileUtil;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
|
||||
//TODO vérifier que le save écrase bien le contenu précédent du fichier
|
||||
public class SimpleSerialFileManager<T> implements ISerialFileManager<T> {
|
||||
|
||||
private Path filePath;
|
||||
private Class<T> clazz;
|
||||
|
||||
SimpleSerialFileManager(String filePath, Class<T> clazz){
|
||||
this(Paths.get(filePath), clazz);
|
||||
}
|
||||
|
||||
SimpleSerialFileManager(Path filePath, Class<T> clazz){
|
||||
this.filePath = filePath;
|
||||
this.clazz = clazz;
|
||||
try{
|
||||
FileUtil.createFileIfNotExist(filePath);
|
||||
}catch (IOException e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<T> loadListFromFile() {
|
||||
try{
|
||||
if(!FileUtil.createFileIfNotExist(filePath)){
|
||||
List<T> loadedEntries = new ArrayList<>();
|
||||
fillCollection(loadedEntries);
|
||||
return loadedEntries;
|
||||
}
|
||||
}catch(IOException | ClassNotFoundException e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<T> loadSetFromFile() {
|
||||
try{
|
||||
if(!FileUtil.createFileIfNotExist(filePath)){
|
||||
Set<T> loadedEntries = new HashSet<>();
|
||||
fillCollection(loadedEntries);
|
||||
return loadedEntries;
|
||||
}
|
||||
}catch(IOException | ClassNotFoundException e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void fillCollection(Collection<T> collection) throws IOException, ClassNotFoundException {
|
||||
if(collection==null) return;
|
||||
try(FileInputStream fis = new FileInputStream(filePath.toString());
|
||||
ObjectInputStream ois = new ObjectInputStream(fis)){
|
||||
boolean end = false;
|
||||
do{
|
||||
try{
|
||||
Object obj = ois.readObject();
|
||||
if(clazz.isInstance(obj)){
|
||||
T ae = clazz.cast(obj);
|
||||
collection.add(ae);
|
||||
}
|
||||
}catch (EOFException e){
|
||||
end = true;
|
||||
}
|
||||
}while(!end);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveListToFile(List<T> list) {
|
||||
save(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveSetToFile(Set<T> set) {
|
||||
save(set);
|
||||
}
|
||||
|
||||
private void save(Collection<T> collection){
|
||||
try{
|
||||
FileUtil.createFileIfNotExist(filePath);
|
||||
}catch (IOException e){
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
try(FileOutputStream fos = new FileOutputStream(filePath.toString());
|
||||
ObjectOutputStream oos = new ObjectOutputStream(fos)){
|
||||
|
||||
collection.forEach(p -> {
|
||||
try {
|
||||
oos.writeObject(p);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}catch(IOException e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package com.pqt.server.tools.io;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class SimpleSerialFileManagerFactory {
|
||||
protected SimpleSerialFileManagerFactory(){}
|
||||
|
||||
public static <T> ISerialFileManager<T> getFileManager(Class<T> clazz, String filePath){
|
||||
return new SimpleSerialFileManager<>(filePath, clazz);
|
||||
}
|
||||
|
||||
public static <T> ISerialFileManager<T> getFileManager(Class<T> clazz, Path filePath){
|
||||
return new SimpleSerialFileManager<>(filePath, clazz);
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.pqt.server.tools.security;
|
||||
|
||||
public interface IHashTool {
|
||||
String hashAndSalt(String str, String salt);
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package com.pqt.server.tools.security;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public class MD5HashTool implements IHashTool{
|
||||
@Override
|
||||
public String hashAndSalt(String input, String salt) {
|
||||
String md5 = null;
|
||||
|
||||
if(input == null || salt == null) return null;
|
||||
|
||||
try {
|
||||
String str = salt+input;
|
||||
//Create MessageDigest object for MD5
|
||||
MessageDigest digest = MessageDigest.getInstance("MD5");
|
||||
|
||||
//Update input string in message digest
|
||||
digest.update(str.getBytes(), 0, str.length());
|
||||
|
||||
//Converts message digest value in base 16 (hex)
|
||||
md5 = new BigInteger(1, digest.digest()).toString(16);
|
||||
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return md5;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user