Commit d414a2ad by Patryk Czarnik

Wielomodulowy-RestSerwer

parent e542dea5
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>pl.alx.kjava.wielomodulowy</groupId>
<artifactId>PC38-Wielomodulowy</artifactId>
<version>1.0</version>
</parent>
<artifactId>PC38-Wielomodulowy-RestSerwer</artifactId>
<packaging>war</packaging>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.2</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>PC38-Wielomodulowy-Model</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>PC38-Wielomodulowy-BazaDanych</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>8.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>fop</artifactId>
<version>2.8</version>
<exclusions>
<exclusion>
<groupId>xml-apis</groupId>
<artifactId>xml-apis</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
\ No newline at end of file
package rest;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
@ApplicationPath("/")
public class AplikacjaRestowa extends Application {
/* Ta klasa pełni rolę "aktywatora" usługi restowej w ramach projektu.
* Gdy serwer zauważy taką klase w projekcie, to skonfiguruje mechanizmy JAX-RS.
* Ta klasa może też zawierać konfigurację:
* - @ApplicationPath - wspólny prefiks przed wszystkimi zapytaniami REST-owymi
*
* - metoda getClasses - zwraca zbiór klas, z których składa się aplikacja
*
* - metoda getSingletons - zwraca obiekty, z których składa się aplikacja
*
* Gdy tych metod nie ma, to serwer skanuje projekt i znajduje wszystkie klasy z adnotacjami @Path lub @Provider
*/
}
package rest;
import java.time.LocalDateTime;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
@Path("/hello")
public class Hello {
@GET
public String helloWorld() {
return "Hello world";
}
@GET
@Path("/time")
public String ktoraGodzina() {
return LocalDateTime.now().toString();
}
}
package rest;
import java.util.List;
import javax.enterprise.context.RequestScoped;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import sklep.db.CustomerDAO;
import sklep.db.DBConnection;
import sklep.db.DBException;
import sklep.db.RecordNotFound;
import sklep.model.Customer;
@RequestScoped
@Path("/customers")
@Produces({ "application/xml", "application/json" })
@Consumes({ "application/xml", "application/json" })
public class RCustomer {
@POST
public Response save(final Customer customer) throws DBException {
try(DBConnection db = DBConnection.open()) {
CustomerDAO customerDAO = db.customerDAO();
customerDAO.save(customer);
db.commit();
return Response.created(UriBuilder.fromResource(RCustomer.class).path(String.valueOf(customer.getEmail())).build()).build();
}
}
@GET
@Path("/{id}")
public Customer findById(@PathParam("id") final String email) throws DBException, RecordNotFound {
try(DBConnection db = DBConnection.open()) {
CustomerDAO customerDAO = db.customerDAO();
return customerDAO.findByEmail(email);
}
}
@GET
public List<Customer> listAll() throws DBException {
try(DBConnection db = DBConnection.open()) {
CustomerDAO customerDAO = db.customerDAO();
return customerDAO.readAll();
}
}
@PUT
@Path("/{id}")
public Response update(@PathParam("id") String email, final Customer customer) throws DBException {
try(DBConnection db = DBConnection.open()) {
CustomerDAO customerDAO = db.customerDAO();
customer.setEmail(email);
customerDAO.save(customer);
db.commit();
}
return Response.noContent().build();
}
@DELETE
@Path("/{id}")
public Response deleteById(@PathParam("id") String email) throws DBException {
try(DBConnection db = DBConnection.open()) {
CustomerDAO customerDAO = db.customerDAO();
customerDAO.delete(email);
db.commit();
}
return Response.noContent().build();
}
}
package rest;
import java.net.URI;
import java.util.List;
import javax.enterprise.context.RequestScoped;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriBuilder;
import sklep.db.DBConnection;
import sklep.db.DBException;
import sklep.db.OrderDAO;
import sklep.db.RecordNotFound;
import sklep.model.Order;
@RequestScoped
@Path("/orders")
@Produces({ "application/xml", "application/json" })
@Consumes({ "application/xml", "application/json" })
public class ROrder {
@GET
public List<Order> listAll() throws DBException {
try(DBConnection db = DBConnection.open()) {
OrderDAO orderDAO = db.orderDAO();
return orderDAO.readAll();
}
}
@GET
@Path("/{id:[0-9][0-9]*}")
public Response findById(@PathParam("id") final Integer id) {
// Klasa Response pozwala nam z pełną precyzją przygotować odpowiedź, która ma zostać odesłana klientowi.
// W przypadku pozytywnym (ok) zostanie odesłany obiekt przetłumaczony na XML lub JSON, a kod wynikowy to będzie 200.
// Ale w przypadku błędów możemy sami zdecydować co odsyłami (tutaj odpowiednie kody HTTP).
try(DBConnection db = DBConnection.open()) {
OrderDAO orderDAO = db.orderDAO();
Order order = orderDAO.findById(id);
return Response.ok(order).build();
} catch (DBException e) {
e.printStackTrace();
return Response.status(Status.INTERNAL_SERVER_ERROR).build();
} catch (RecordNotFound e) {
return Response.status(Status.NOT_FOUND).build();
}
}
/*
// Metoda, która ma obsłużyć pobranie info o właścicielu zamówienia:
// /orders/1/customer
// W tej wersji metoda zwraca bezpośrednio dane klienta.
// Wada tego podejścia: ten sam rekord (konkretny klient) jest widoczny pod różnymi adresami URL.
@GET
@Path("/{id:[0-9][0-9]*}/customer")
public Customer getCustomer(@PathParam("id") Integer orderId) throws DBException, RecordNotFound {
try(DBConnection db = DBConnection.open()) {
OrderDAO orderDAO = db.orderDAO();
CustomerDAO customerDAO = db.customerDAO();
Order order = orderDAO.findById(orderId);
Customer customer = customerDAO.findByEmail(order.getCustomerEmail());
return customer;
}
}
*/
// W tej wersji w odpowiedzi na zapytanie o dane klienta, który złożył zamówienie,
// wyślemy przekierowanie pod adres tego klienta.
// To jest lepsze z punktu widzenia "dobrych praktyk REST".
@GET
@Path("/{id:[0-9][0-9]*}/customer")
public Response getCustomer(@PathParam("id") Integer orderId) throws DBException, RecordNotFound {
try(DBConnection db = DBConnection.open()) {
OrderDAO orderDAO = db.orderDAO();
Order order = orderDAO.findById(orderId);
URI customerURI = UriBuilder.fromResource(RCustomer.class)
.path("/{email}")
.build(order.getCustomerEmail());
return Response.seeOther(customerURI).build();
}
}
}
package rest;
import java.math.BigDecimal;
import java.net.URI;
import java.util.List;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import sklep.db.DBConnection;
import sklep.db.DBException;
import sklep.db.ProductDAO;
import sklep.db.RecordNotFound;
import sklep.model.Product;
import sklep.model.ProductList;
import sklep.photo.PhotoUtil;
// JAX-RS - część Javy EE
// Aplikacja (projekt) składa się z wielu "resource classes", z których każda obsługuje pewien rodzaj encji
// np. pod adresem /products działa ta klasa RProduct, która zajmuje się obsługą produktów
// a pod adresem /orders działa ROrder, która zajmuje się zamówieniami.
// Przykładowa dobra (szczegółowa) dokumentacja: https://eclipse-ee4j.github.io/jersey.github.io/documentation/latest3x/index.html
@Path("/products")
public class RProduct {
// Gdy w Produces jest wiele formatów, to klient może wybrać za pomocą nagłówka Accept
// Gdy w Consumes jest wiele formatów, to klient może przysłać dane w dowolnym z nich (nagłówek Content-Type)
@GET
@Produces({"application/json", "application/xml", "application/pdf", "text/plain;charset=UTF-8"})
public ProductList readAllProducts() throws DBException {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
return new ProductList(productDAO.readAll());
}
}
// Może też być tak, że kilka metod działa pod tym samym adresem, ale służą one do tworzenia odpowiedzi w różnych formatach.
// Przykład: tworzenie HTML w oddzielnej metodzie
@GET
@Produces("text/html;charset=UTF-8")
public String readAllProductsHTML() throws DBException {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
List<Product> products = productDAO.readAll();
StringBuilder txt = new StringBuilder("<!DOCTYPE html>\n<html><body>\n");
txt.append("<h1>Lista produktów</h1>\n");
for(Product product : products) {
txt.append(product.toHtml()).append('\n');
}
txt.append("</body></html>");
return txt.toString();
}
}
@Path("/{id}")
@GET
@Produces({"application/json", "application/xml", "application/pdf", "text/plain"})
public Product readOneProduct(@PathParam("id") int productId) throws DBException, RecordNotFound {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
return productDAO.findById(productId);
}
}
@Path("/{id}")
@GET
@Produces("text/html;charset=UTF-8")
public String readOneProductsHTML(@PathParam("id") int productId) throws DBException, RecordNotFound {
Product product = readOneProduct(productId);
return "<!DOCTYPE html>\n<html><body>" + product.toHtml() + "</body></html>";
}
@Path("/{id}/foto")
@GET
@Produces("image/jpeg")
public byte[] foto(@PathParam("id") int productId) throws DBException, RecordNotFound {
return PhotoUtil.readBytes(productId);
}
@Path("/{id}/price")
@GET
@Produces({"application/json", "text/plain"})
public BigDecimal getPrice(@PathParam("id") int productId) throws DBException, RecordNotFound {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
Product product = productDAO.findById(productId);
return product.getPrice();
}
}
// W metodach HTTP POST i PUT klient przysyła dane na serwer (tzw. body / entity / content).
// Adnotacja @Consumes mówi w jakim formacie powinny być te dane.
// Metoda w JAX-RS może posiadać co najwyżej jeden parametr nieoznaczony żadną adnotacją.
// Właśnie poprzez ten parametr przekazywane są dane przysłane w treści zapytania (w praktyce: POST i PUT).
@Path("/{id}/price")
@PUT
@Consumes({"application/json", "text/plain"})
public void setPrice(@PathParam("id") int productId, BigDecimal newPrice) throws DBException, RecordNotFound {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
Product product = productDAO.findById(productId);
product.setPrice(newPrice);
productDAO.save(product);
db.commit();
}
}
@Path("/{id}")
@PUT
@Consumes({"application/json", "application/xml"})
public void updateProduct(@PathParam("id") int productId, Product product) throws DBException {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
if(product.getProductId() == null) {
// jeśli przysłany rekord nie posiada ID, to utawiam ID z adresu
// w szczególności pozwalam na zapisanie nowego produktu, ale pod określonym adresem
product.setProductId(productId);
} else if(product.getProductId() != productId) {
// ale jeśli ID było ustawione, ale było inne niż w adresie, to jest to podejrzana sytuacja i takie zapytania nie wykonamy
throw new IllegalArgumentException("ID produktu nie zgodne z adresem URL");
}
productDAO.save(product);
db.commit();
}
}
// za pomocą POST będzie można przysłać wyłącznie nowe produkty be określonego ID,
// a aplikacja sama wybierze nowe ID z sekwencjii
// w wyniku zostanie odesłany uzupełniony produkt
@POST
@Consumes({"application/json", "application/xml"})
public Response addProduct(Product product,
@Context UriInfo uriInfo) throws DBException {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
if(product.getProductId() != null) {
throw new IllegalArgumentException("Nowy produkt nie może mieć ustawionego ID");
}
productDAO.insertNew(product);
db.commit();
URI uri = uriInfo.getAbsolutePathBuilder().path("/{id}").build(product.getProductId());
return Response.created(uri).build();
}
}
@DELETE
@Path("/{id}")
public void removeProduct(@PathParam("id") int productId) throws DBException, RecordNotFound {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
if(productDAO.delete(productId)) {
db.commit();
} else {
throw new RecordNotFound("Nie ma produktu nr " + productId);
}
}
}
}
package rest;
import java.math.BigDecimal;
import java.util.List;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import sklep.db.DBConnection;
import sklep.db.DBException;
import sklep.db.ProductDAO;
import sklep.db.RecordNotFound;
import sklep.model.Product;
import sklep.photo.PhotoUtil;
@Path("/products.json")
@Consumes("application/json")
@Produces("application/json")
public class RProductJSON {
// @Produces i @Consumes na poziomie klasy są domyślnymi ustawieniami dla wszystkich metod.
// Metoda może to ustawienie zmienić - tak jest w foto.
// Inaczej niż w Springu, ustawienie Produces/Consumes na poziomie klasy dotyczy tylko tych metod,
// które coś produkują lub konsumują. Metody typu GET nie zwracają uwagi na Consumes,
// a metody typu void są poprawne mimo tego, że nie produkują JSONa.
@GET
public List<Product> readAllProducts() throws DBException {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
return productDAO.readAll();
}
}
@Path("/{id}")
@GET
public Product readOneProduct(@PathParam("id") int productId) throws DBException, RecordNotFound {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
return productDAO.findById(productId);
}
}
@Path("/{id}/foto")
@GET
@Produces("image/jpeg")
public byte[] foto(@PathParam("id") int productId) throws DBException, RecordNotFound {
return PhotoUtil.readBytes(productId);
}
@Path("/{id}/price")
@GET
public BigDecimal getPrice(@PathParam("id") int productId) throws DBException, RecordNotFound {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
Product product = productDAO.findById(productId);
return product.getPrice();
}
}
// W metodach HTTP POST i PUT klient przysyła dane na serwer (tzw. body / entity / content).
// Adnotacja @Consumes mówi w jakim formacie powinny być te dane.
// Metoda w JAX-RS może posiadać co najwyżej jeden parametr nieoznaczony żadną adnotacją.
// Właśnie poprzez ten parametr przekazywane są dane przysłane w treści zapytania (w praktyce: POST i PUT).
@Path("/{id}/price")
@PUT
public void setPrice(@PathParam("id") int productId, BigDecimal newPrice) throws DBException, RecordNotFound {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
Product product = productDAO.findById(productId);
product.setPrice(newPrice);
productDAO.save(product);
db.commit();
}
}
@Path("/{id}")
@PUT
public void updateProduct(@PathParam("id") int productId, Product product) throws DBException {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
if(product.getProductId() == null) {
// jeśli przysłany rekord nie posiada ID, to utawiam ID z adresu
// w szczególności pozwalam na zapisanie nowego produktu, ale pod określonym adresem
product.setProductId(productId);
} else if(product.getProductId() != productId) {
// ale jeśli ID było ustawione, ale było inne niż w adresie, to jest to podejrzana sytuacja i takie zapytania nie wykonamy
throw new IllegalArgumentException("ID produktu nie zgodne z adresem URL");
}
productDAO.save(product);
db.commit();
}
}
// za pomocą POST będzie można przysłać wyłącznie nowe produkty be określonego ID,
// a aplikacja sama wybierze nowe ID z sekwencjii
// w wyniku zostanie odesłany uzupełniony produkt
@POST
public Product addProduct(Product product) throws DBException {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
if(product.getProductId() != null) {
throw new IllegalArgumentException("Nowy produkt nie może mieć ustawionego ID");
}
productDAO.insertNew(product);
db.commit();
return product;
}
}
@DELETE
@Path("/{id}")
public void removeProduct(@PathParam("id") int productId) throws DBException, RecordNotFound {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
if(productDAO.delete(productId)) {
db.commit();
} else {
throw new RecordNotFound("Nie ma produktu nr " + productId);
}
}
}
}
package rest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import sklep.db.DBConnection;
import sklep.db.DBException;
import sklep.db.ProductDAO;
import sklep.db.RecordNotFound;
import sklep.model.Product;
import sklep.model.ProductList;
@Path("/products.pdf")
@Produces("application/pdf")
public class RProductPDF {
@GET
public ProductList readAllProducts() throws DBException {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
return new ProductList(productDAO.readAll());
}
}
@Path("/{id}")
@GET
public Product readOneProduct(@PathParam("id") int productId) throws DBException, RecordNotFound {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
return productDAO.findById(productId);
}
}
}
package rest;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import sklep.db.DBConnection;
import sklep.db.DBException;
import sklep.db.ProductDAO;
import sklep.db.RecordNotFound;
import sklep.model.Price;
import sklep.model.Product;
import sklep.model.ProductList;
import sklep.photo.PhotoUtil;
@Path("/products.xml")
@Consumes("application/xml")
@Produces("application/xml")
public class RProductXML {
@GET
public ProductList readAllProducts() throws DBException {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
return new ProductList(productDAO.readAll());
}
}
@Path("/{id}")
@GET
public Product readOneProduct(@PathParam("id") int productId) throws DBException, RecordNotFound {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
return productDAO.findById(productId);
}
}
@Path("/{id}/foto")
@GET
@Produces("image/jpeg")
public byte[] foto(@PathParam("id") int productId) throws DBException, RecordNotFound {
return PhotoUtil.readBytes(productId);
}
@Path("/{id}/price")
@GET
public Price getPrice(@PathParam("id") int productId) throws DBException, RecordNotFound {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
Product product = productDAO.findById(productId);
return new Price(product.getPrice());
}
}
@Path("/{id}/price")
@PUT
public void setPrice(@PathParam("id") int productId, Price newPrice) throws DBException, RecordNotFound {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
Product product = productDAO.findById(productId);
product.setPrice(newPrice.getValue());
productDAO.save(product);
db.commit();
}
}
@Path("/{id}")
@PUT
public void updateProduct(@PathParam("id") int productId, Product product) throws DBException {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
if(product.getProductId() == null) {
product.setProductId(productId);
} else if(product.getProductId() != productId) {
throw new IllegalArgumentException("ID produktu nie zgodne z adresem URL");
}
productDAO.save(product);
db.commit();
}
}
@POST
public Product addProduct(Product product) throws DBException {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
if(product.getProductId() != null) {
throw new IllegalArgumentException("Nowy produkt nie może mieć ustawionego ID");
}
productDAO.insertNew(product);
db.commit();
return product;
}
}
@DELETE
@Path("/{id}")
public void removeProduct(@PathParam("id") int productId) throws DBException, RecordNotFound {
try(DBConnection db = DBConnection.open()) {
ProductDAO productDAO = db.productDAO();
if(productDAO.delete(productId)) {
db.commit();
} else {
throw new RecordNotFound("Nie ma produktu nr " + productId);
}
}
}
}
package rest.ext;
import java.io.BufferedOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import javax.servlet.ServletContext;
import javax.ws.rs.WebApplicationException;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.util.JAXBSource;
import javax.xml.transform.Result;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamSource;
import org.apache.fop.apps.Fop;
import org.apache.fop.apps.FopFactory;
import org.apache.xmlgraphics.util.MimeConstants;
public class ObslugaXSL {
private ServletContext servletContext;
public ObslugaXSL(ServletContext servletContext) {
this.servletContext = servletContext;
}
// obiekt -(za pomocą JAXB)-> XML -(za pomocą transformera)-> XML(XSL-FO)
// --(za pomocą Apache FOP)-> PDF -> output
public void wypiszPDF(Object obj, OutputStream output) {
try(InputStream configStream = servletContext.getResourceAsStream("WEB-INF/fop-conf.xml")) {
JAXBContext ctx = JAXBContext.newInstance(obj.getClass());
JAXBSource src = new JAXBSource(ctx, obj);
FopFactory fopFactory = FopFactory.newInstance(new URI(""), configStream);
try(BufferedOutputStream pdfOut = new BufferedOutputStream(output)) {
Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, pdfOut);
TransformerFactory tf = TransformerFactory.newInstance();
StreamSource xsl = new StreamSource(servletContext.getResourceAsStream("WEB-INF/sklep-fo.xsl"));
Transformer tr = tf.newTransformer(xsl);
Result res = new SAXResult(fop.getDefaultHandler());
tr.transform(src, res);
}
} catch (Exception e) {
e.printStackTrace();
throw new WebApplicationException("Problem FOP " + e.getMessage(), e, 500);
}
}
}
package rest.ext;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import javax.servlet.ServletContext;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
import sklep.model.Product;
import sklep.model.ProductList;
@Provider
public class PDFWriter implements MessageBodyWriter<Object> {
private static final MediaType PDF_TYPE = new MediaType("application", "pdf");
@Context
private ServletContext servletContext;
@Override
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return (type == ProductList.class || type == Product.class) && PDF_TYPE.isCompatible(mediaType);
}
@Override
public void writeTo(Object obj, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders, OutputStream output)
throws IOException, WebApplicationException {
String fileName = "products.pdf";
if (obj instanceof Product) {
Product product = (Product) obj;
fileName = product.getProductName().replace(' ', '_') + ".pdf";
}
// httpHeaders.add("Content-Disposition", "attachment;filename=" + fileName);
httpHeaders.add("Content-Disposition", "inline;filename=" + fileName);
ObslugaXSL obslugaXSL = new ObslugaXSL(servletContext);
obslugaXSL.wypiszPDF(obj, output);
}
}
package rest.ext;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import sklep.db.RecordNotFound;
@Provider
public class RecordNotFoundMapper implements ExceptionMapper<RecordNotFound> {
@Override
public Response toResponse(RecordNotFound exception) {
String html = "<html><body><h1>Nie znaleziono</h1><p style='color:red'>"
+ exception.getMessage() + "</p></body></html>";
return Response.status(404)
.type("text/html;charset=utf-8")
.entity(html)
.build();
}
}
package rest.ext;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import sklep.db.SklepException;
@Provider
public class SklepExceptionMapper implements ExceptionMapper<SklepException> {
@Override
public Response toResponse(SklepException e) {
String tresc = String.format("<html><body>"
+ "<h1>Błąd</h1>"
+ "<div>Błąd typu <code>%s</code></div>"
+ "<div style='color:red'>%s</div>"
+ "</body></html>",
e.getClass().getName(),
e.getMessage());
return Response.status(Status.INTERNAL_SERVER_ERROR)
.type("text/html;charset=utf-8")
.entity(tresc)
.build();
}
}
// a gdybyśmy chcieli mapować wszystkie wyjątki
// public class DefaultExceptionMapper implements ExceptionMapper<Exception> { ... }
<?xml version="1.0"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- $Id: fop.xconf 447325 2006-09-18 08:55:33Z jeremias $ -->
<!--
This is an example configuration file for FOP.
This file contains the same settings as the default values
and will have no effect if used unchanged.
Relative config url's will be resolved relative to
the location of this file.
-->
<!-- NOTE: This is the version of the configuration -->
<fop version="1.0">
<!-- Base URL for resolving relative URLs -->
<base>.</base>
<!-- Source resolution in dpi (dots/pixels per inch) for determining the size of pixels in SVG and bitmap images, default: 72dpi -->
<source-resolution>72</source-resolution>
<!-- Target resolution in dpi (dots/pixels per inch) for specifying the target resolution for generated bitmaps, default: 72dpi -->
<target-resolution>72</target-resolution>
<!-- Default page-height and page-width, in case
value is specified as auto -->
<default-page-settings height="11in" width="8.26in"/>
<!-- Information for specific renderers -->
<!-- Uses renderer mime type for renderers -->
<renderers>
<renderer mime="application/pdf">
<filterList>
<!-- provides compression using zlib flate (default is on) -->
<value>flate</value>
<!-- encodes binary data into printable ascii characters (default off)
This provides about a 4:5 expansion of data size -->
<!-- <value>ascii-85</value> -->
<!-- encodes binary data with hex representation (default off)
This filter is not recommended as it doubles the data size -->
<!-- <value>ascii-hex</value> -->
</filterList>
<fonts>
<!-- embedded fonts -->
<!--
This information must exactly match the font specified
in the fo file. Otherwise it will use a default font.
For example,
<fo:inline font-family="Arial" font-weight="bold" font-style="normal">
Arial-normal-normal font
</fo:inline>
for the font triplet specified by:
<font-triplet name="Arial" style="normal" weight="bold"/>
If you do not want to embed the font in the pdf document
then do not include the "embed-url" attribute.
The font will be needed where the document is viewed
for it to be displayed properly.
possible styles: normal | italic | oblique | backslant
possible weights: normal | bold | 100 | 200 | 300 | 400
| 500 | 600 | 700 | 800 | 900
(normal = 400, bold = 700)
-->
<!--
<font kerning="yes" embed-url="/usr/share/fonts/truetype/msttcorefonts/arial.ttf">
<font-triplet name="Arial" style="normal" weight="normal"/>
<font-triplet name="ArialMT" style="normal" weight="normal"/>
</font>
<font kerning="yes" embed-url="/usr/share/fonts/truetype/msttcorefonts/arial.ttf">
<font-triplet name="Arial" style="normal" weight="normal"/>
<font-triplet name="ArialMT" style="normal" weight="normal"/>
</font>
-->
<!-- PC -->
<directory recursive="true">/usr/share/fonts/truetype/</directory>
<directory recursive="true">C:\Windows\Fonts</directory>
</fonts>
<!-- This option lets you specify additional options on an XML handler -->
<!--xml-handler namespace="http://www.w3.org/2000/svg">
<stroke-text>false</stroke-text>
</xml-handler-->
</renderer>
<renderer mime="application/postscript">
<!-- This option forces the PS renderer to rotate landscape pages -->
<!--auto-rotate-landscape>true</auto-rotate-landscape-->
<!-- This option lets you specify additional options on an XML handler -->
<!--xml-handler namespace="http://www.w3.org/2000/svg">
<stroke-text>false</stroke-text>
</xml-handler-->
</renderer>
<renderer mime="application/vnd.hp-PCL">
</renderer>
<!-- MIF does not have a renderer
<renderer mime="application/vnd.mif">
</renderer>
-->
<renderer mime="image/svg+xml">
<format type="paginated"/>
<link value="true"/>
<strokeText value="false"/>
</renderer>
<renderer mime="application/awt">
</renderer>
<renderer mime="image/png">
<!--transparent-page-background>true</transparent-page-background-->
</renderer>
<renderer mime="image/tiff">
<!--transparent-page-background>true</transparent-page-background-->
<!--compression>CCITT T.6</compression-->
</renderer>
<renderer mime="text/xml">
</renderer>
<!-- RTF does not have a renderer
<renderer mime="text/rtf">
</renderer>
-->
<renderer mime="text/plain">
<pageSize columns="80"/>
</renderer>
</renderers>
</fop>
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:output method="xml" encoding="utf-8" />
<xsl:template match="/">
<fo:root font-family="Arial">
<fo:layout-master-set>
<fo:simple-page-master master-name="A4"
page-width="210mm" page-height="297mm" margin-top="1cm"
margin-bottom="1cm" margin-left="1.5cm" margin-right="1.5cm">
<fo:region-body margin="2cm" />
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="A4">
<fo:flow flow-name="xsl-region-body">
<xsl:apply-templates />
<fo:block/>
</fo:flow>
</fo:page-sequence>
</fo:root>
</xsl:template>
<xsl:template match="product">
<fo:block-container space-before.minimum="0.5em"
page-break-inside="avoid">
<fo:block>
<xsl:text>Produkt nr </xsl:text>
<xsl:value-of select="@id" />
<xsl:text>.</xsl:text>
</fo:block>
<fo:block-container margin="1em"
border-style="solid" border-width="2.5pt" padding="3pt"
border-color="#2233AA">
<fo:block font-weight="bold" font-size="14pt"
margin-bottom="1em" color="#FF2244">
<xsl:apply-templates select="product-name" />
</fo:block>
<fo:block font-weight="bold" color="green">
<xsl:text>Cena: </xsl:text>
<xsl:value-of select="price" />
</fo:block>
<fo:block color="green">
<xsl:text>VAT: </xsl:text>
<fo:inline font-style="italic">
<xsl:value-of select="vat * 100" />
<xsl:text>%</xsl:text>
</fo:inline>
</fo:block>
<fo:block font-size="12pt" margin-bottom="1em">
<xsl:apply-templates select="description" />
</fo:block>
</fo:block-container>
</fo:block-container>
</xsl:template>
</xsl:stylesheet>
......@@ -13,5 +13,6 @@
<modules>
<module>PC38-Wielomodulowy-Model</module>
<module>PC38-Wielomodulowy-BazaDanych</module>
<module>PC38-Wielomodulowy-RestSerwer</module>
</modules>
</project>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment