[P25284-20] Initial commit with setup and first draft rest servcice

This commit is contained in:
verboomp
2026-01-20 11:39:25 +01:00
parent 071b1f7f95
commit 9d36ea7780
66 changed files with 4981 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
package marketing.heyday.hartmann.fotodocumentation.core.db.migration;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import jakarta.ejb.Singleton;
import jakarta.ejb.Startup;
import jakarta.ejb.TransactionManagement;
import jakarta.ejb.TransactionManagementType;
import javax.sql.DataSource;
import org.flywaydb.core.Flyway;
/**
*
*
* <p>Copyright: Copyright (c) 2024</p>
* <p>Company: heyday Marketing GmbH</p>
* @author <a href="mailto:p.verboom@heyday.marketing">Patrick Verboom</a>
* @version 1.0
*
* created: 19 Jan 2026
*/
@Singleton
@Startup
@TransactionManagement(TransactionManagementType.BEAN)
public class DbMigrator {
@Resource(lookup = "java:/jdbc/fotoDocumentationDS")
private DataSource dataSource;
@PostConstruct
public void onStartup() {
Flyway flyway = getFlyway();
flyway.setDataSource(dataSource);
flyway.setTable("DB_MIGRATION");
flyway.setLocations(this.getClass().getPackage().getName());
flyway.setBaselineOnMigrate(false);
flyway.setValidateOnMigrate(false);
flyway.setIgnoreFailedFutureMigration(true);
flyway.migrate();
}
protected Flyway getFlyway() {
return new Flyway();
}
}

View File

@@ -0,0 +1,71 @@
package marketing.heyday.hartmann.fotodocumentation.core.model;
import java.util.Date;
import jakarta.persistence.*;
import com.fasterxml.jackson.annotation.JsonIgnore;
/**
*
*
* <p>Copyright: Copyright (c) 2024</p>
* <p>Company: heyday Marketing GmbH</p>
* @author <a href="mailto:p.verboom@heyday.marketing">Patrick Verboom</a>
* @version 1.0
*
* created: 19 Jan 2026
*/
@MappedSuperclass
public abstract class AbstractDateEntity extends AbstractEntity {
private static final long serialVersionUID = 1L;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "JPA_CREATED", nullable = false)
@JsonIgnore
private Date jpaCreated;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "JPA_UPDATED", nullable = false)
@JsonIgnore
private Date jpaUpdated;
@Column(name = "JPA_ACTIVE", nullable = false)
private boolean active = true;
@Version
@Column(name = "JPA_VERSION", nullable = false)
private int jpaVersion = 0;
@PrePersist
protected void onCreate() {
Date now = new Date();
jpaCreated = now;
jpaUpdated = now;
}
@PreUpdate
protected void onUpdate() {
jpaUpdated = new Date();
}
public Date getJpaCreated() {
return jpaCreated;
}
public Date getJpaUpdated() {
return jpaUpdated;
}
public int getJpaVersion() {
return jpaVersion;
}
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
}

View File

@@ -0,0 +1,21 @@
package marketing.heyday.hartmann.fotodocumentation.core.model;
import java.io.Serializable;
import jakarta.persistence.MappedSuperclass;
/**
*
*
* <p>Copyright: Copyright (c) 2024</p>
* <p>Company: heyday Marketing GmbH</p>
* @author <a href="mailto:p.verboom@heyday.marketing">Patrick Verboom</a>
* @version 1.0
*
* created: 19 Jan 2026
*/
@MappedSuperclass
public abstract class AbstractEntity implements Serializable {
private static final long serialVersionUID = 1L;
}

View File

@@ -0,0 +1,108 @@
package marketing.heyday.hartmann.fotodocumentation.core.model;
import java.util.HashSet;
import java.util.Set;
import org.apache.commons.lang.builder.HashCodeBuilder;
import jakarta.persistence.*;
/**
*
*
* <p>Copyright: Copyright (c) 2024</p>
* <p>Company: heyday Marketing GmbH</p>
* @author <a href="mailto:p.verboom@heyday.marketing">Patrick Verboom</a>
* @version 1.0
*
* created: 19 Jan 2026
*/
@Entity
@Table(name = "customer")
@NamedQuery(name = Customer.FIND_BY_NUMBER, query = "select c from Customer c where c.customerNumber = :cutomerNumber")
public class Customer extends AbstractDateEntity {
private static final long serialVersionUID = 1L;
public static final String SEQUENCE = "customer_seq";
public static final String FIND_BY_NUMBER = "Customer.findByNumber";
public static final String PARAM_NUMBER = "cutomerNumber";
@Id
@Column(name = "customer_id", length = 22)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE)
@SequenceGenerator(name = SEQUENCE, sequenceName = SEQUENCE, allocationSize = 1)
private Long customerId;
@Column(name = "customer_number", unique = true, nullable = false)
private String customerNumber;
@Column(name = "name", nullable = false)
private String name;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "customer_id_fk")
private Set<Picture> pictures = new HashSet<>();
public Long getCustomerId() {
return customerId;
}
public void setCustomerId(Long customerId) {
this.customerId = customerId;
}
public String getCustomerNumber() {
return customerNumber;
}
public void setCustomerNumber(String customerNumber) {
this.customerNumber = customerNumber;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Picture> getPictures() {
return pictures;
}
public void setPictures(Set<Picture> pictures) {
this.pictures = pictures;
}
@Override
public int hashCode() {
return new HashCodeBuilder().append(customerNumber).toHashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == null || this.getClass() != obj.getClass()) {
return false;
}
return this.customerNumber.equals(((Customer) obj).getCustomerNumber());
}
public static class Builder {
private Customer instance = new Customer();
public Builder customerNumber(String customerNumber) {
instance.setCustomerNumber(customerNumber);
return this;
}
public Builder name(String name) {
instance.setName(name);
return this;
}
public Customer build() {
return instance;
}
}
}

View File

@@ -0,0 +1,125 @@
package marketing.heyday.hartmann.fotodocumentation.core.model;
import java.util.Date;
import org.apache.commons.lang.builder.HashCodeBuilder;
import jakarta.persistence.*;
/**
*
*
* <p>Copyright: Copyright (c) 2024</p>
* <p>Company: heyday Marketing GmbH</p>
* @author <a href="mailto:p.verboom@heyday.marketing">Patrick Verboom</a>
* @version 1.0
*
* created: 19 Jan 2026
*/
@Entity
@Table(name = "picture")
public class Picture extends AbstractDateEntity {
private static final long serialVersionUID = 1L;
public static final String SEQUENCE = "picture_seq";
@Id
@Column(name = "picture_id", length = 22)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE)
@SequenceGenerator(name = SEQUENCE, sequenceName = SEQUENCE, allocationSize = 1)
private Long pictureId;
// username from the person that shot the picture
@Column(name = "username")
private String username;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "picture_date", nullable = false)
private Date pictureDate;
private String comment;
@Column(name = "image")
private String image;
public Long getPictureId() {
return pictureId;
}
public void setPictureId(Long pictureId) {
this.pictureId = pictureId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Date getPictureDate() {
return pictureDate;
}
public void setPictureDate(Date pictureDate) {
this.pictureDate = pictureDate;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
@Override
public int hashCode() {
return new HashCodeBuilder().append(pictureId).toHashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == null || this.getClass() != obj.getClass() || pictureId == null) {
return false;
}
return this.pictureId.equals(((Picture) obj).getPictureId());
}
public static class Builder {
private Picture instance = new Picture();
public Builder username(String username) {
instance.setUsername(username);
return this;
}
public Builder pictureDate(Date pictureDate) {
instance.setPictureDate(pictureDate);
return this;
}
public Builder comment(String comment) {
instance.setComment(comment);
return this;
}
public Builder image(String image) {
instance.setImage(image);
return this;
}
public Picture build() {
return instance;
}
}
}

View File

@@ -0,0 +1,95 @@
package marketing.heyday.hartmann.fotodocumentation.core.model;
import java.util.HashSet;
import java.util.Set;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import jakarta.persistence.*;
/**
*
* <p>Copyright: Copyright (c) 2024</p>
* <p>Company: heyday Marketing GmbH</p>
* @author <a href="mailto:p.verboom@heyday.marketing">Patrick Verboom</a>
* @version 1.0
*
* created: 19 Jan 2026
*/
@Entity
@Table(name = "x_right")
public class Right extends AbstractDateEntity {
private static final long serialVersionUID = 1L;
public static final String SEQUENCE = "right_seq";
@Id
@Column(name = "right_id", length = 22)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE)
@SequenceGenerator(name = SEQUENCE, sequenceName = SEQUENCE, allocationSize = 1)
private Long rightId;
@Column(nullable = false, unique = true)
private String code;
@Column(nullable = false, unique = true)
private String name;
@ManyToMany(mappedBy = "rights")
private Set<User> roles = new HashSet<>();
public Long getRightId() {
return rightId;
}
public void setRightId(Long rightId) {
this.rightId = rightId;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int hashCode() {
return new HashCodeBuilder().append(code).toHashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == null || this.getClass() != obj.getClass()) {
return false;
}
return this.code.equals(((Right) obj).getCode());
}
public static class Builder {
private Right instance = new Right();
public Builder code(String code) {
instance.setCode(code);
return this;
}
public Builder name(String name) {
instance.setName(name);
return this;
}
public Right build() {
return instance;
}
}
}

View File

@@ -0,0 +1,179 @@
package marketing.heyday.hartmann.fotodocumentation.core.model;
import java.util.HashSet;
import java.util.Set;
import org.apache.commons.lang.builder.HashCodeBuilder;
import jakarta.persistence.*;
/**
*
*
* <p>Copyright: Copyright (c) 2024</p>
* <p>Company: heyday Marketing GmbH</p>
* @author <a href="mailto:p.verboom@heyday.marketing">Patrick Verboom</a>
* @version 1.0
*
* created: 19 Jan 2026
*/
@Entity
@Table(name = "x_user")
public class User extends AbstractDateEntity {
private static final long serialVersionUID = 1L;
public static final String SEQUENCE = "user_seq";
@Id
@Column(name = "user_id", length = 22)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE)
@SequenceGenerator(name = SEQUENCE, sequenceName = SEQUENCE, allocationSize = 1)
private Long userId;
@Column(name = "username", unique = true, nullable = false)
private String username;
@Column(nullable = false)
private String password;
private String salt;
@Column(name = "title")
private String title;
@Column(nullable = false)
private String firstname;
@Column(nullable = false)
private String lastname;
@Column(nullable = false)
private String email;
@ManyToMany
@JoinTable(name = "user_to_right", joinColumns = { @JoinColumn(name = "user_id_fk") }, inverseJoinColumns = { @JoinColumn(name = "right_id_fk") })
private Set<Right> rights = new HashSet<>();
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getSalt() {
return salt;
}
public void setSalt(String salt) {
this.salt = salt;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Set<Right> getRights() {
return rights;
}
public void setRights(Set<Right> rights) {
this.rights = rights;
}
@Override
public int hashCode() {
return new HashCodeBuilder().append(username).toHashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == null || this.getClass() != obj.getClass()) {
return false;
}
return this.username.equals(((User) obj).getUsername());
}
public static class Builder {
private User instance = new User();
public Builder email(String email) {
instance.setEmail(email);
return this;
}
public Builder firstname(String firstname) {
instance.setFirstname(firstname);
return this;
}
public Builder lastname(String lastname) {
instance.setLastname(lastname);
return this;
}
public Builder title(String title) {
instance.setTitle(title);
return this;
}
public Builder username(String username) {
instance.setUsername(username);
return this;
}
public Builder password(String password) {
instance.setPassword(password);
return this;
}
public User build() {
return instance;
}
}
}

View File

@@ -0,0 +1,16 @@
package marketing.heyday.hartmann.fotodocumentation.core.query;
/**
*
*
* <p>Copyright: Copyright (c) 2024</p>
* <p>Company: heyday Marketing GmbH</p>
* @author <a href="mailto:p.verboom@heyday.marketing">Patrick Verboom</a>
* @version 1.0
*
* created: 19 Jan 2026
*/
public record Param(String name, Object value) {
}

View File

@@ -0,0 +1,100 @@
package marketing.heyday.hartmann.fotodocumentation.core.query;
import java.util.Arrays;
import java.util.Collection;
import java.util.Optional;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import jakarta.annotation.security.PermitAll;
import jakarta.ejb.Stateless;
import jakarta.persistence.EntityManager;
import jakarta.persistence.NoResultException;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.Query;
/**
*
*
* <p>Copyright: Copyright (c) 2024</p>
* <p>Company: heyday Marketing GmbH</p>
* @author <a href="mailto:p.verboom@heyday.marketing">Patrick Verboom</a>
* @version 1.0
*
* created: 19 Jan 2026
*/
@Stateless
@SuppressWarnings("unchecked")
@PermitAll
public class QueryService {
private static final Log LOG = LogFactory.getLog(QueryService.class);
@PersistenceContext
private EntityManager eManager;
public int count(String namedQuery, Param... params) {
Query query = eManager.createNamedQuery(namedQuery);
for (Param param : params) {
query.setParameter(param.name(), param.value());
}
return ((Number) query.getSingleResult()).intValue();
}
public <T> Optional<T> callNamedQuerySingleResult(String namedQuery, Param... params) {
return singleResult(eManager.createNamedQuery(namedQuery), Arrays.asList(params));
}
private <T> Optional<T> singleResult(Query query, Collection<Param> params) {
try {
for (Param param : params) {
query.setParameter(param.name(), param.value());
}
return Optional.ofNullable((T) query.getSingleResult());
} catch (NoResultException nre) {
LOG.debug("No entity found for query " + query + " with params " + params);
LOG.trace("NoResultException", nre);
return Optional.empty();
}
}
public <T> T callNamedQueryList(String namedQuery, Param... objects) {
Query query = eManager.createNamedQuery(namedQuery);
for (Param param : objects) {
query.setParameter(param.name(), param.value());
}
return (T) query.getResultList();
}
public <T> Optional<T> callQuerySingleResult(String sql, Param... objects) {
Query query = eManager.createQuery(sql);
for (Param param : objects) {
query.setParameter(param.name(), param.value());
}
return Optional.of((T) query.getSingleResult());
}
public <T> T callNativeQuery(String sql, Param... objects) {
Query query = eManager.createNativeQuery(sql);
for (Param param : objects) {
query.setParameter(param.name(), param.value());
}
return (T) query.getResultList();
}
public int callNativeQueryUpdate(String sql, Param... objects) {
Query query = eManager.createNativeQuery(sql);
for (Param param : objects) {
query.setParameter(param.name(), param.value());
}
return query.executeUpdate();
}
public int callNamedQueryUpdate(String namedQuery, Param... objects) {
Query query = eManager.createNamedQuery(namedQuery);
for (Param param : objects) {
query.setParameter(param.name(), param.value());
}
return query.executeUpdate();
}
}

View File

@@ -0,0 +1,48 @@
package marketing.heyday.hartmann.fotodocumentation.core.service;
import java.util.Optional;
import jakarta.ejb.EJB;
import jakarta.ejb.LocalBean;
import jakarta.ejb.Stateless;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import marketing.heyday.hartmann.fotodocumentation.core.model.Customer;
import marketing.heyday.hartmann.fotodocumentation.core.model.Picture;
import marketing.heyday.hartmann.fotodocumentation.core.query.Param;
import marketing.heyday.hartmann.fotodocumentation.core.query.QueryService;
import marketing.heyday.hartmann.fotodocumentation.rest.vo.CustomerPictureValue;
/**
*
* <p>Copyright: Copyright (c) 2024</p>
* <p>Company: heyday Marketing GmbH</p>
* @author <a href="mailto:p.verboom@heyday.marketing">Patrick Verboom</a>
* @version 1.0
*
* created: 19 Jan 2026
*/
@Stateless
@LocalBean
public class CustomerPictureService {
@PersistenceContext
private EntityManager entityManager;
@EJB
private QueryService queryService;
public boolean addCustomerPicture(CustomerPictureValue customerPictureValue) {
Optional<Customer> customerOpt = queryService.callNamedQuerySingleResult(Customer.FIND_BY_NUMBER, new Param(Customer.PARAM_NUMBER, customerPictureValue.customerNumber()));
Customer customer = customerOpt.orElseGet(() -> new Customer.Builder().customerNumber(customerPictureValue.customerNumber()).name(customerPictureValue.pharmacyName()).build());
Picture picture = new Picture.Builder().username(customerPictureValue.username()).comment(customerPictureValue.comment()).image(customerPictureValue.base64String()).pictureDate(customerPictureValue.date()).build();
customer.getPictures().add(picture);
entityManager.persist(picture);
entityManager.merge(customer);
entityManager.flush();
return true;
}
}

View File

@@ -0,0 +1,49 @@
package marketing.heyday.hartmann.fotodocumentation.rest;
import org.jboss.resteasy.annotations.GZIP;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import jakarta.ejb.EJB;
import jakarta.enterprise.context.RequestScoped;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import marketing.heyday.hartmann.fotodocumentation.core.service.CustomerPictureService;
import marketing.heyday.hartmann.fotodocumentation.rest.jackson.JsonSchemaValidate;
import marketing.heyday.hartmann.fotodocumentation.rest.vo.CustomerPictureValue;
/**
*
* <p>Copyright: Copyright (c) 2024</p>
* <p>Company: heyday Marketing GmbH</p>
* @author <a href="mailto:p.verboom@heyday.marketing">Patrick Verboom</a>
* @version 1.0
*
* created: 19 Jan 2026
*/
@RequestScoped
@Path("customer-picture")
public class CustomerPictureResource {
@EJB
private CustomerPictureService customerPictureService;
@GZIP
@POST
@Path("")
//@Authenticate(shouldBeInAllRoles = RightUtils.ADMIN_RIGHT)
@Consumes(MediaType.APPLICATION_JSON)
@Operation(summary = "Add Customer Image to database")
@ApiResponse(responseCode = "200", description = "Add successfull")
public Response doAddCustomerPicture(@JsonSchemaValidate("schema/customer_picture_add.json") CustomerPictureValue customerPictureValue) {
boolean success = customerPictureService.addCustomerPicture(customerPictureValue);
return success ? Response.ok().build() : Response.status(Status.BAD_REQUEST).build();
}
}

View File

@@ -0,0 +1,36 @@
package marketing.heyday.hartmann.fotodocumentation.rest;
import jakarta.enterprise.context.RequestScoped;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
/**
*
* <p>Copyright: Copyright (c) 2024</p>
* <p>Company: heyday Marketing GmbH</p>
* @author <a href="mailto:p.verboom@heyday.marketing">Patrick Verboom</a>
* @version 1.0
*
* created: 14 Nov 2024
*/
@Path("monitoring")
@RequestScoped
public class MonitoringResource {
@GET
@Path("check/{text}")
@Produces(MediaType.TEXT_PLAIN)
@Operation(summary = "Monitoring service for testing if the server is up and running.")
@ApiResponse(responseCode = "200", description = "ok with as body the given path param text. ")
public Response check(@PathParam("text") String text) {
return Response.status(Status.OK).entity(text).build();
}
}

View File

@@ -0,0 +1,51 @@
package marketing.heyday.hartmann.fotodocumentation.rest.jackson;
import java.util.HashSet;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import io.swagger.v3.jaxrs2.integration.resources.OpenApiResource;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Contact;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.annotations.servers.Server;
import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;
import jakarta.ws.rs.core.MediaType;
import marketing.heyday.hartmann.fotodocumentation.rest.CustomerPictureResource;
import marketing.heyday.hartmann.fotodocumentation.rest.MonitoringResource;
/**
*
*
* <p>Copyright: Copyright (c) 2017</p>
* <p>Company: heyday marketing GmbH</p>
* @author <a href="mailto:p.verboom@heyday.marketing">Patrick Verboom</a>
* @version 1.0
*
* created: 18 Oct 2017
*/
@OpenAPIDefinition(info = @Info(title = "Hartmann Photo upload API", version = "1.0", description = "All available routes for the Hartmann Photo upload API", contact = @Contact(url = "https://localhost", name = "Patrick Verboom", email = "p.verboom@heyday.marketing")), servers = {
@Server(description = "development", url = "http://localhost"),
@Server(description = "integration", url = "http://localhost"),
@Server(description = "production", url = "http://localhost")
})
@ApplicationPath("/api")
public class ApplicationConfigApi extends Application {
private static final Log LOG = LogFactory.getLog(ApplicationConfigApi.class);
public static final String JSON_OUT = MediaType.APPLICATION_JSON + "; charset=utf-8";
@Override
public Set<Class<?>> getClasses() {
Set<Class<?>> retVal = new HashSet<>();
retVal.add(OpenApiResource.class);
retVal.add(ValidatedMessageBodyReader.class);
//retVal.add(AuthenticateFilter.class);
retVal.add(MonitoringResource.class);
retVal.add(CustomerPictureResource.class);
LOG.info("returning rest api classes " + retVal);
return retVal;
}
}

View File

@@ -0,0 +1,21 @@
package marketing.heyday.hartmann.fotodocumentation.rest.jackson;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
* <p>Copyright: Copyright (c) 2017</p>
* <p>Company: heyday marketing GmbH</p>
* @author <a href="mailto:p.verboom@heyday.marketing">Patrick Verboom</a>
* @version 1.0
*
* created: Feb 10, 2017
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value={ElementType.PARAMETER})
public @interface JsonSchemaValidate {
String value();
}

View File

@@ -0,0 +1,14 @@
package marketing.heyday.hartmann.fotodocumentation.rest.jackson;
/**
*
* <p>Copyright: Copyright (c) 2017</p>
* <p>Company: heyday marketing GmbH</p>
* @author <a href="mailto:p.verboom@heyday.marketing">Patrick Verboom</a>
* @version 1.0
*
* created: Feb 10, 2017
*/
public interface SchemaValidated {
}

View File

@@ -0,0 +1,114 @@
package marketing.heyday.hartmann.fotodocumentation.rest.jackson;
import java.io.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.ext.MessageBodyReader;
import jakarta.ws.rs.ext.Provider;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.networknt.schema.JsonSchema;
import com.networknt.schema.JsonSchemaFactory;
import com.networknt.schema.ValidationMessage;
/**
*
*
* <p>Copyright: Copyright (c) 2024</p>
* <p>Company: heyday Marketing GmbH</p>
* @author <a href="mailto:p.verboom@heyday.marketing">Patrick Verboom</a>
* @version 1.0
*
* created: 6 Dec 2024
*/
@Provider
@Consumes(value = {
MediaType.APPLICATION_JSON, "application/json; charset=utf-8"
})
public class ValidatedMessageBodyReader implements MessageBodyReader<SchemaValidated> {
private static final Log LOG = LogFactory.getLog(ValidatedMessageBodyReader.class);
/* (non-Javadoc)
* @see jakarta.ws.rs.ext.MessageBodyReader#isReadable(java.lang.Class, java.lang.reflect.Type, java.lang.annotation.Annotation[], jakarta.ws.rs.core.MediaType)
*/
@Override
public boolean isReadable(Class<?> classType, Type type, Annotation[] annotations, MediaType mediaType) {
if (mediaType.getType().contains("application/json")) {
return false;
}
return Arrays.stream(annotations).anyMatch(a -> a.annotationType() == JsonSchemaValidate.class);
}
/* (non-Javadoc)
* @see jakarta.ws.rs.ext.MessageBodyReader#readFrom(java.lang.Class, java.lang.reflect.Type, java.lang.annotation.Annotation[], jakarta.ws.rs.core.MediaType, jakarta.ws.rs.core.MultivaluedMap, java.io.InputStream)
*/
@Override
public SchemaValidated readFrom(Class<SchemaValidated> classType, Type type, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, String> httpHeaders, InputStream input) throws IOException {
final String jsonData = read(input);
Optional<JsonSchemaValidate> annotation = Arrays.stream(annotations).filter(a -> a.annotationType() == JsonSchemaValidate.class).map(a -> (JsonSchemaValidate) a).findAny();
if (annotation.isPresent()) {
ValidationReply reply = validate(annotation.get(), jsonData);
if (!reply.success) {
throw new WebApplicationException(reply.getErrorResponse());
}
}
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(new StringReader(jsonData), classType);
}
/**
* @param jsonSchema
* @param jsonData
* @return
*/
private ValidationReply validate(JsonSchemaValidate jsonSchema, String jsonData) {
String schemaPath = jsonSchema.value();
try {
JsonSchema schema = getJsonSchema(schemaPath);
JsonNode node = getJsonNode(jsonData);
Set<ValidationMessage> errors = schema.validate(node);
if (!errors.isEmpty()) {
LOG.error("Failed to validate json to schema " + schemaPath);
errors.stream().forEach(LOG::error);
}
return new ValidationReply.Builder().success(errors.isEmpty()).errors(errors).build();
} catch (IOException e) {
LOG.error(e.getMessage(), e);
return new ValidationReply.Builder().success(false).build();
}
}
protected JsonSchema getJsonSchema(String name) throws IOException {
JsonSchemaFactory factory = new JsonSchemaFactory();
try (InputStream input = Thread.currentThread().getContextClassLoader().getResourceAsStream(name);) {
return factory.getSchema(input);
}
}
protected JsonNode getJsonNode(String content) throws IOException {
return new ObjectMapper().readTree(content);
}
private String read(InputStream input) throws IOException {
try (BufferedReader buffer = new BufferedReader(new InputStreamReader(input, Charset.forName("UTF-8")))) {
return buffer.lines().collect(Collectors.joining("\n"));
}
}
}

View File

@@ -0,0 +1,51 @@
package marketing.heyday.hartmann.fotodocumentation.rest.jackson;
import java.util.HashSet;
import java.util.Set;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import com.networknt.schema.ValidationMessage;
/**
*
* <p>Copyright: Copyright (c) 2017</p>
* <p>Company: heyday marketing GmbH</p>
* @author <a href="mailto:p.verboom@heyday.marketing">Patrick Verboom</a>
* @version 1.0
*
* created: Feb 14, 2017
*/
public final class ValidationReply {
public final boolean success;
public final Set<ValidationMessage> errors = new HashSet<>();
private ValidationReply(boolean success, Set<ValidationMessage> errors) {
this.success = success;
this.errors.addAll(errors);
}
public Response getErrorResponse() {
return Response.status(Status.BAD_REQUEST).entity(errors).build();
}
public static final class Builder {
private boolean success;
private Set<ValidationMessage> errors = new HashSet<>();
public Builder success(boolean success) {
this.success = success;
return this;
}
public Builder errors(Set<ValidationMessage> errors) {
this.errors = errors;
return this;
}
public ValidationReply build() {
return new ValidationReply(success, errors);
}
}
}

View File

@@ -0,0 +1,17 @@
package marketing.heyday.hartmann.fotodocumentation.rest.vo;
import java.util.Date;
/**
*
* <p>Copyright: Copyright (c) 2024</p>
* <p>Company: heyday Marketing GmbH</p>
* @author <a href="mailto:p.verboom@heyday.marketing">Patrick Verboom</a>
* @version 1.0
*
* created: 19 Jan 2026
*/
public record CustomerPictureValue(String username, String pharmacyName, String customerNumber, Date date, String comment, String base64String) {
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/beans_4_0.xsd"
version="4.0" bean-discovery-mode="all">
</beans>

View File

@@ -0,0 +1,24 @@
<persistence version="3.0" xmlns="https://jakarta.ee/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence
https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd">
<persistence-unit name="auth" transaction-type="JTA">
<jta-data-source>java:/jdbc/fotoDocumentationDS</jta-data-source>
<class>marketing.heyday.hartmann.fotodocumentation.core.model.Right</class>
<class>marketing.heyday.hartmann.fotodocumentation.core.model.User</class>
<class>marketing.heyday.hartmann.fotodocumentation.core.model.Customer</class>
<class>marketing.heyday.hartmann.fotodocumentation.core.model.Picture</class>
<properties>
<property name="hibernate.format_sql" value="false" />
<property name="hibernate.show_sql" value="false" />
<!-- <property name="hibernate.archive.autodetection" value="class" /> -->
<property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect" />
<property name="hibernate.hbm2ddl.auto" value="none" />
<property name="hibernate.jpa.compliance.query" value="false" />
</properties>
</persistence-unit>
</persistence>

View File

@@ -0,0 +1,76 @@
-- Right
create sequence right_seq start 25;
create table x_right (
right_id bigint PRIMARY KEY,
code varchar(50) NOT NULL,
name varchar(150) NOT NULL,
jpa_active boolean NOT NULL,
jpa_created timestamp NOT NULL,
jpa_updated timestamp NOT NULL,
jpa_version integer NOT NULL,
CONSTRAINT unq_x_right_code UNIQUE(code)
);
-- user
create sequence user_seq start 25;
create table x_user (
user_id bigint PRIMARY KEY,
username varchar(150) NOT NULL,
password varchar(150) NOT NULL,
salt varchar(150) NOT NULL,
title varchar(15) ,
firstname varchar(150) NOT NULL,
lastname varchar(150) NOT NULL,
email varchar(150) NOT NULL,
jpa_active boolean NOT NULL,
jpa_created timestamp NOT NULL,
jpa_updated timestamp NOT NULL,
jpa_version integer NOT NULL,
CONSTRAINT unq_x_user_username UNIQUE(username)
);
create table user_to_right (
user_id_fk bigint REFERENCES x_user,
right_id_fk bigint REFERENCES x_right,
PRIMARY KEY(user_id_fk, right_id_fk)
);
-- customer
create sequence customer_seq start 25;
create table customer (
customer_id bigint PRIMARY KEY,
customer_number varchar(150) NOT NULL,
name varchar(150) NOT NULL,
jpa_active boolean NOT NULL,
jpa_created timestamp NOT NULL,
jpa_updated timestamp NOT NULL,
jpa_version integer NOT NULL,
CONSTRAINT unq_customer_number UNIQUE(customer_number)
);
-- picture
create sequence picture_seq start 25;
create table picture (
picture_id bigint PRIMARY KEY,
username varchar(150),
picture_date timestamp NOT NULL,
comment TEXT,
image TEXT,
jpa_active boolean NOT NULL,
jpa_created timestamp NOT NULL,
jpa_updated timestamp NOT NULL,
jpa_version integer NOT NULL,
customer_id_fk bigint REFERENCES customer
);

View File

@@ -0,0 +1,40 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Add Customer Picture",
"description": "Add a Customer Picture to the system",
"type": "object",
"properties": {
"username": {
"description": "The username from the user who uploads the picture",
"type": "string"
},
"pharmacyName": {
"description": "The Name from the pharmacy customer ",
"type": "string"
},
"customerNumber": {
"description": "The unique number from the pharmacy customer ",
"type": "string"
},
"date": {
"description": "The date when the picture is taken ",
"type": "string"
},
"comment": {
"description": "A free text comment field ",
"type": "string"
},
"base64String": {
"description": "The Picture content as base64 ",
"type": "string"
}
},
"required": [
"username",
"pharmacyName",
"customerNumber",
"date",
"comment",
"base64String"
]
}