/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.ttech.cordovabuild.web;
import com.google.common.io.ByteStreams;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.ttech.cordovabuild.domain.application.Application;
import com.ttech.cordovabuild.domain.application.*;
import com.ttech.cordovabuild.domain.asset.AssetRef;
import com.ttech.cordovabuild.domain.asset.AssetService;
import com.ttech.cordovabuild.domain.asset.InputStreamHandler;
import com.ttech.cordovabuild.infrastructure.queue.BuiltQueuePublisher;
import net.sf.uadetector.OperatingSystemFamily;
import net.sf.uadetector.UserAgent;
import net.sf.uadetector.UserAgentStringParser;
import net.sf.uadetector.service.UADetectorServiceFactory;
import org.hibernate.validator.constraints.NotEmpty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.mobile.device.Device;
import org.springframework.mobile.device.DeviceResolver;
import org.springframework.mobile.device.LiteDeviceResolver;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.context.WebApplicationContext;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
import javax.imageio.stream.MemoryCacheImageOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.MessageFormat;
import java.util.List;
@Component
@Scope(WebApplicationContext.SCOPE_REQUEST)
@Produces({MediaType.APPLICATION_JSON})
public class ApplicationResource {
private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationResource.class);
@Autowired
ApplicationService service;
@Autowired
BuiltQueuePublisher queuePublisher;
@Autowired
AssetService assetService;
DeviceResolver deviceResolver = new LiteDeviceResolver();
@GET
public List<ApplicationBuilt> getApplications() {
return service.getApplications(SecurityContextHolder.getContext().getAuthentication().getName());
}
@GET
@Path("/{id}")
public ApplicationBuilt getApplication(@PathParam("id") Long id) {
LOGGER.info("get application with {} and application service {}", id, service);
return service.getLatestBuilt(id);
}
@PUT
@Path("/{id}")
public Application updateApplication(@PathParam("id") Long id) {
return service.updateApplicationCode(id);
}
@POST
@Path("/{id}/build")
public ApplicationBuilt rebuildApplication(@PathParam("id") Long id) {
ApplicationBuilt applicationBuilt = service.prepareApplicationBuilt(id);
queuePublisher.publishBuilt(applicationBuilt);
return applicationBuilt;
}
@GET
@Path("/{id}/qrimage")
@Produces("image/jpeg")
public StreamingImageOutput getQrImage(@PathParam("id") Long id, @QueryParam("width") @DefaultValue("150") int width, @QueryParam("height") @DefaultValue("150") int height, @Context UriInfo uriInfo) {
QRCodeWriter qrCodeWriter = new QRCodeWriter();
String url = uriInfo.getRequestUriBuilder().path("..").path("install").queryParam("qrkey", service.findApplication(id).getQrKey()).build().normalize().toString();
try {
BitMatrix bitMatrix = qrCodeWriter.encode(url, BarcodeFormat.QR_CODE, width, height);
BufferedImage image = MatrixToImageWriter.toBufferedImage(bitMatrix);
return new StreamingImageOutput(image);
} catch (WriterException e) {
throw new QrCodeException(e);
}
}
@GET
@Path("/{id}/icon")
@Produces("image/png")
public Response getIcon(@PathParam("id") Long id) {
ApplicationBuilt latestBuilt = service.getLatestBuilt(id);
AssetRef iconAssetRef = latestBuilt.getApplication().getApplicationConfig().getIconAssetRef();
if (iconAssetRef == null) {
return Response.status(404).build();
} else {
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
assetService.handleInputStream(iconAssetRef, new InputStreamHandler() {
@Override
public void handleInputStream(InputStream inputStream) throws IOException {
ByteStreams.copy(inputStream, outputStream);
}
});
return Response.ok(outputStream.toByteArray(), "image/png").build();
}
}
@GET
@Path("/{id}/download/{type}")
@Produces({BuiltType.Constants.ANDROID_MIME_TYPE, BuiltType.Constants.IOS_MIME_TYPE})
public Response getBuiltAsset(@PathParam("id") Long id, @PathParam("type") String typeValue, @Context HttpServletResponse httpServletResponse) throws IOException {
try {
BuiltType type = BuiltType.getValueOfIgnoreCase(typeValue);
LOGGER.info("application asset download for id:{} and type:{} requested", id, type);
ApplicationBuilt latestBuilt = service.getLatestBuilt(id);
return getBuiltAssetInternal(httpServletResponse, type, latestBuilt);
} catch (IllegalArgumentException e) {
return Response.status(404).build();
}
}
private Response getBuiltAssetInternal(HttpServletResponse httpServletResponse, BuiltType type, ApplicationBuilt latestBuilt) throws IOException {
LOGGER.info("application built with id:{} found", latestBuilt.getId());
for (BuiltTarget builtTarget : latestBuilt.getBuiltTargets()) {
if (builtTarget.getType().equals(type)) {
LOGGER.info("built target for {} found with status {}", builtTarget.getType(), builtTarget.getStatus());
if (builtTarget.getStatus().equals(BuiltTarget.Status.SUCCESS)) {
httpServletResponse.setHeader("Content-Type", type.getMimeType());
httpServletResponse.setHeader("content-disposition", MessageFormat.format("attachment; filename = {0}.{1}",
latestBuilt.getBuiltConfig().getApplicationName(), type.getPlatformSuffix()));
write(httpServletResponse.getOutputStream(), builtTarget.getAssetRef());
return Response.ok().build();
} else {
httpServletResponse.setStatus(Response.Status.NOT_FOUND.getStatusCode());
return Response.status(Response.Status.NOT_FOUND).build();
}
}
}
LOGGER.info("no build target found for {}", type);
return Response.status(Response.Status.NOT_FOUND).build();
}
@GET
@Path("/{id}/install")
@Produces({BuiltType.Constants.ANDROID_MIME_TYPE, BuiltType.Constants.IOS_MIME_TYPE})
public Response getWithQrKey(@PathParam("id") Long id, @NotEmpty @QueryParam("qrkey") String qrKey, @Context HttpServletRequest httpServletRequest, @Context HttpServletResponse httpServletResponse) throws IOException {
ApplicationBuilt latestBuilt = service.getLatestBuilt(id);
UserAgent userAgent = getUserAgent(httpServletRequest);
Device currentDevice = deviceResolver.resolveDevice(httpServletRequest);
LOGGER.info("userAgent:{} and device:{}",userAgent,currentDevice);
boolean knownFamily = userAgent != null && (userAgent.getOperatingSystem().getFamily().equals(OperatingSystemFamily.ANDROID) || userAgent.getOperatingSystem().getFamily().equals(OperatingSystemFamily.IOS));
knownFamily = knownFamily && currentDevice != null && (currentDevice.isMobile() || currentDevice.isTablet());
if (latestBuilt.getApplication().getQrKey().equalsIgnoreCase(qrKey) && knownFamily) {
try {
return getBuiltAssetInternal(httpServletResponse, BuiltType.getValueOfIgnoreCase(userAgent.getOperatingSystem().getFamily().getName()), latestBuilt);
} catch (IllegalArgumentException e) {
return Response.status(404).build();
}
} else {
return Response.status(404).build();
}
}
private UserAgent getUserAgent(HttpServletRequest httpServletRequest) {
try {
UserAgentStringParser parser = UADetectorServiceFactory.getResourceModuleParser();
return parser.parse(httpServletRequest.getHeader("User-Agent"));
} catch (Exception e) {
LOGGER.error("could not determine agent", e);
}
return null;
}
@POST
public ApplicationBuilt createApplication(@QueryParam("sourceUri") String sourceUri) {
Application application = service.createApplication(SecurityContextHolder.getContext().getAuthentication().getName(), sourceUri);
ApplicationBuilt applicationBuilt = service.prepareApplicationBuilt(application);
queuePublisher.publishBuilt(applicationBuilt);
return applicationBuilt;
}
class StreamingImageOutput implements StreamingOutput {
private BufferedImage image;
public StreamingImageOutput(BufferedImage image) {
this.image = image;
}
public void write(OutputStream output) throws IOException, WebApplicationException {
JPEGImageWriteParam jpegParams = new JPEGImageWriteParam(null);
jpegParams.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
jpegParams.setCompressionQuality(1f);
ImageWriter writer = ImageIO.getImageWritersByFormatName("jpeg").next();
MemoryCacheImageOutputStream out = new MemoryCacheImageOutputStream(output);
writer.setOutput(out);
writer.write(null, new IIOImage(image, null, null), jpegParams);
writer.dispose();
out.close();
}
}
private void write(final OutputStream output, AssetRef assetRef) throws IOException, WebApplicationException {
assetService.handleInputStream(assetRef, new InputStreamHandler() {
@Override
public void handleInputStream(InputStream inputStream) throws IOException {
ByteStreams.copy(inputStream, output);
}
});
}
}