com.axelor.web.service.RestService.java Source code

Java tutorial

Introduction

Here is the source code for com.axelor.web.service.RestService.java

Source

/**
 * Axelor Business Solutions
 *
 * Copyright (C) 2005-2016 Axelor (<http://axelor.com>).
 *
 * This program is free software: you can redistribute it and/or  modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.axelor.web.service;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
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.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.StreamingOutput;
import javax.xml.bind.DatatypeConverter;

import org.jboss.resteasy.plugins.providers.multipart.InputPart;
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;

import com.axelor.app.AppSettings;
import com.axelor.auth.AuthUtils;
import com.axelor.common.ClassUtils;
import com.axelor.common.FileUtils;
import com.axelor.common.StringUtils;
import com.axelor.db.JPA;
import com.axelor.db.JpaRepository;
import com.axelor.db.Model;
import com.axelor.db.Query;
import com.axelor.db.Repository;
import com.axelor.db.mapper.Mapper;
import com.axelor.inject.Beans;
import com.axelor.mail.db.MailAddress;
import com.axelor.mail.db.MailFollower;
import com.axelor.mail.db.MailMessage;
import com.axelor.mail.db.repo.MailFollowerRepository;
import com.axelor.mail.db.repo.MailMessageRepository;
import com.axelor.mail.service.MailService;
import com.axelor.mail.web.MailController;
import com.axelor.meta.ActionHandler;
import com.axelor.meta.MetaFiles;
import com.axelor.meta.MetaStore;
import com.axelor.meta.db.MetaAttachment;
import com.axelor.meta.db.MetaFile;
import com.axelor.meta.db.repo.MetaFileRepository;
import com.axelor.meta.schema.actions.Action;
import com.axelor.meta.service.MetaService;
import com.axelor.rpc.ActionRequest;
import com.axelor.rpc.ActionResponse;
import com.axelor.rpc.Context;
import com.axelor.rpc.Request;
import com.axelor.rpc.Response;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.CaseFormat;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.primitives.Longs;
import com.google.inject.servlet.RequestScoped;

@RequestScoped
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("/rest/{model}")
public class RestService extends ResourceService {

    @Inject
    private MetaService service;

    @Inject
    private MailMessageRepository messages;

    @Inject
    private MailFollowerRepository followers;

    @GET
    public Response find(@QueryParam("limit") @DefaultValue("40") int limit,
            @QueryParam("offset") @DefaultValue("0") int offset, @QueryParam("q") String query) {

        Request request = new Request();
        request.setOffset(offset);
        request.setLimit(limit);
        return getResource().search(request);
    }

    @POST
    @Path("search")
    @SuppressWarnings("all")
    public Response find(Request request) {

        request.setModel(getModel());

        final Map<String, Object> data = request.getData();
        if (data == null || !data.containsKey("_domainAction")) {
            return getResource().search(request);
        }

        // domain actions are used by portlets to evaluate view context
        // in the context of the current form

        final String action = (String) data.get("_domainAction");
        final Action act = MetaStore.getAction(action);
        final ActionRequest actRequest = new ActionRequest();

        actRequest.setModel(getModel());
        actRequest.setAction(action);
        actRequest.setData(data);

        final ActionHandler actHandler = new ActionHandler(actRequest);
        final Object res = act.evaluate(actHandler);

        if (res instanceof Map) {
            Map<String, Object> old = (Map) data.get("_domainContext");
            Map<String, Object> ctx = (Map) ((Map) res).get("context");
            if (old == null) {
                old = ctx;
            } else {
                old.putAll(ctx);
            }
        }

        return getResource().search(request);
    }

    @POST
    public Response save(Request request) {
        request.setModel(getModel());
        return getResource().save(request);
    }

    @PUT
    public Response create(Request request) {
        request.setModel(getModel());
        return getResource().save(request);
    }

    @GET
    @Path("{id}")
    public Response read(@PathParam("id") long id) {
        return getResource().read(id);
    }

    @POST
    @Path("{id}/fetch")
    public Response fetch(@PathParam("id") long id, Request request) {
        request.setModel(getModel());
        Response response = getResource().fetch(id, request);
        long attachments = Query.of(MetaAttachment.class)
                .filter("self.objectId = ?1 AND self.objectName = ?2", id, getModel()).cacheable().count();

        if (response.getItem(0) != null) {
            @SuppressWarnings("all")
            Map<String, Object> item = (Map) response.getItem(0);
            item.put("$attachments", attachments);
        }

        return response;
    }

    @POST
    @Path("{id}")
    public Response update(@PathParam("id") long id, Request request) {
        final List<Object> records = new ArrayList<>();
        final Map<String, Object> data = request.getData();
        data.put("id", id);
        records.add(data);

        request.setRecords(records);
        request.setModel(getModel());

        return getResource().save(request);
    }

    @POST
    @Path("updateMass")
    public Response updateMass(Request request) {
        request.setModel(getModel());
        return getResource().updateMass(request);
    }

    @DELETE
    @Path("{id}")
    public Response delete(@PathParam("id") long id, @QueryParam("version") int version) {
        Request request = new Request();
        request.setModel(getModel());
        request.setData(ImmutableMap.of("id", (Object) id, "version", version));
        return getResource().remove(id, request);
    }

    @POST
    @Path("{id}/remove")
    public Response remove(@PathParam("id") long id, Request request) {
        request.setModel(getModel());
        return getResource().remove(id, request);
    }

    @POST
    @Path("removeAll")
    public Response remove(Request request) {
        request.setModel(getModel());
        return getResource().remove(request);
    }

    @GET
    @Path("{id}/copy")
    public Response copy(@PathParam("id") long id) {
        return getResource().copy(id);
    }

    @GET
    @Path("{id}/details")
    public Response details(@PathParam("id") long id, @QueryParam("name") String name) {
        Request request = new Request();
        Map<String, Object> data = new HashMap<String, Object>();

        data.put("id", id);
        request.setModel(getModel());
        request.setData(data);
        request.setFields(Lists.newArrayList(name));

        return getResource().getRecordName(request);
    }

    private static final String DEFAULT_UPLOAD_PATH = "{java.io.tmpdir}/axelor/attachments";
    private static String uploadPath = AppSettings.get().getPath("file.upload.dir", DEFAULT_UPLOAD_PATH);

    private void uploadSave(InputStream in, OutputStream out) throws IOException {
        int read = 0;
        byte[] bytes = new byte[1024];
        while ((read = in.read(bytes)) != -1) {
            out.write(bytes, 0, read);
        }
        out.close();
    }

    private String getFileName(MultivaluedMap<String, String> headers) {
        final String[] parts = headers.getFirst("Content-Disposition").split(";");
        for (String filename : parts) {
            if ((filename.trim().startsWith("filename"))) {
                String name = filename.split("=")[1].trim();
                // remove quotes
                return name.replaceAll("^\"|\"$", "");
            }
        }
        return null;
    }

    @POST
    @Path("upload")
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    public Response upload(final MultipartFormDataInput input) throws IOException {

        final Map<String, List<InputPart>> formData = input.getFormDataMap();
        final InputPart filePart = formData.get("file").get(0);
        final InputPart requestPart = formData.get("request").get(0);
        final InputPart fieldPart = formData.get("field").get(0);

        final boolean isAttachment = MetaFile.class.getName().equals(getModel());
        final String field = fieldPart.getBodyAsString();
        final String fileName = getFileName(filePart.getHeaders());
        final InputStream fileStream = filePart.getBody(InputStream.class, null);
        final Request request = Beans.get(ObjectMapper.class).readValue(requestPart.getBodyAsString(),
                Request.class);

        request.setModel(getModel());

        final Map<String, Object> data = request.getData();

        if (!isAttachment) {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            uploadSave(fileStream, out);
            data.put(field, out.toByteArray());
            return getResource().save(request);
        }

        final MetaFiles files = Beans.get(MetaFiles.class);
        final MetaFileRepository repo = Beans.get(MetaFileRepository.class);
        final MetaFile metaFile = Mapper.toBean(MetaFile.class, data);

        MetaFile entity = metaFile;
        if (metaFile.getId() != null) {
            entity = repo.find(metaFile.getId());
        }

        entity.setFileName(metaFile.getFileName());
        entity.setFileType(metaFile.getFileType());

        File tmp = files.upload(fileStream, 0, -1, fileName);
        entity = files.upload(tmp, entity);

        final Response response = new Response();
        response.setData(Arrays.asList(entity));
        response.setStatus(Response.STATUS_SUCCESS);

        return response;
    }

    private static final String BLANK_IMAGE = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";

    @GET
    @Path("{id}/{field}/download")
    @Produces(MediaType.APPLICATION_OCTET_STREAM)
    @SuppressWarnings("all")
    public javax.ws.rs.core.Response download(@PathParam("id") Long id, @PathParam("field") String field,
            @QueryParam("image") boolean isImage) throws IOException {

        boolean isAttachment = MetaFile.class.getName().equals(getModel());

        Class klass = getResource().getModel();
        Mapper mapper = Mapper.of(klass);
        Model bean = JPA.find(klass, id);

        if (isAttachment) {
            final String fileName = (String) mapper.get(bean, "fileName");
            final String filePath = (String) mapper.get(bean, "filePath");
            final File inputFile = FileUtils.getFile(uploadPath, filePath);
            if (!inputFile.exists()) {
                return javax.ws.rs.core.Response.status(Status.NOT_FOUND).build();
            }
            return javax.ws.rs.core.Response.ok(new StreamingOutput() {

                @Override
                public void write(OutputStream output) throws IOException, WebApplicationException {
                    uploadSave(new FileInputStream(inputFile), output);
                }
            }).header("Content-Disposition", "attachment; filename=\"" + fileName + "\"").build();
        }

        String fileName = getModel() + "_" + field;
        Object data = mapper.get(bean, field);

        if (isImage) {
            String base64 = BLANK_IMAGE;
            if (data instanceof byte[]) {
                base64 = new String((byte[]) data);
            }
            try {
                base64 = base64.substring(base64.indexOf(";base64,") + 8);
                data = DatatypeConverter.parseBase64Binary(base64);
            } catch (Exception e) {
            }
            return javax.ws.rs.core.Response.ok(data).build();
        }

        fileName = fileName.replaceAll("\\s", "") + "_" + id;
        fileName = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, fileName);

        if (data == null) {
            return javax.ws.rs.core.Response.noContent().build();
        }

        return javax.ws.rs.core.Response.ok(data)
                .header("Content-Disposition", "attachment; filename=\"" + fileName + "\"").build();
    }

    @POST
    @Path("{id}/attachment")
    public Response attachment(@PathParam("id") long id, Request request) {
        return service.getAttachment(id, getModel(), request);
    }

    @POST
    @Path("removeAttachment")
    public Response removeAttachment(Request request) {
        request.setModel(getModel());
        return service.removeAttachment(request, uploadPath);
    }

    @POST
    @Path("{id}/addAttachment")
    public Response addAttachment(@PathParam("id") long id, Request request) {
        request.setModel(getModel());
        return service.addAttachment(id, request);
    }

    @POST
    @Path("verify")
    public Response verify(Request request) {
        request.setModel(getModel());
        return getResource().verify(request);
    }

    @GET
    @Path("perms")
    public Response perms(@QueryParam("action") String action, @QueryParam("id") Long id) {
        if (action != null && id != null) {
            return getResource().perms(id, action);
        }
        if (id != null) {
            return getResource().perms(id);
        }
        return getResource().perms();
    }

    private static Charset csvCharset = Charsets.ISO_8859_1;
    static {
        try {
            csvCharset = Charset.forName(AppSettings.get().get("data.export.encoding"));
        } catch (Exception e) {
        }
    }

    @GET
    @Path("export/{name}")
    @Produces("text/csv")
    public StreamingOutput export(@PathParam("name") final String name) {

        final java.nio.file.Path temp = MetaFiles.findTempFile(name);
        if (Files.notExists(temp)) {
            throw new IllegalArgumentException(name);
        }

        return new StreamingOutput() {

            @Override
            public void write(OutputStream output) throws IOException, WebApplicationException {
                try (final InputStream is = new FileInputStream(temp.toFile())) {
                    uploadSave(is, output);
                } finally {
                    Files.deleteIfExists(temp);
                }
            }
        };
    }

    @POST
    @Path("export")
    public Response export(Request request) {
        final Response response = new Response();
        final Map<String, Object> data = new HashMap<>();
        request.setModel(getModel());
        try {
            final java.nio.file.Path tempFile = MetaFiles.createTempFile(null, ".csv");
            try (final OutputStream os = new FileOutputStream(tempFile.toFile())) {
                try (final Writer writer = new OutputStreamWriter(os, csvCharset)) {
                    getResource().export(request, writer);
                }
            }
            ;
            data.put("fileName", tempFile.toFile().getName());
            response.setData(data);
        } catch (IOException e) {
            response.setException(e);
        }

        return response;
    }

    @GET
    @Path("messages")
    public Response messages(@QueryParam("folder") String folder, @QueryParam("parent") Long parentId,
            @QueryParam("count") boolean count, @QueryParam("limit") @DefaultValue("10") Integer limit,
            @QueryParam("offset") @DefaultValue("0") Integer offset, @QueryParam("relatedId") Long relatedId,
            @QueryParam("relatedModel") String relatedModel) {

        final MailController ctrl = Beans.get(MailController.class);

        final ActionRequest req = new ActionRequest();
        final ActionResponse res = new ActionResponse();

        req.setModel(relatedModel);
        req.setOffset(offset);
        req.setLimit(limit);

        Class<?> relatedClass = null;
        Model related = null;

        try {
            relatedClass = ClassUtils.findClass(relatedModel);
        } catch (Exception e) {
        }

        if (relatedClass != null && relatedId != null) {
            @SuppressWarnings("all")
            JpaRepository<?> repo = JpaRepository.of((Class) relatedClass);
            if (repo != null) {
                related = repo.find(relatedId);
            }
        }

        if (related != null) {
            final List<Object> records = new ArrayList<>();
            records.add(related);
            req.setRecords(records);
            ctrl.related(req, res);
            return res;
        }

        if (count) {
            ctrl.unread(req, res);
            return res;
        }

        if (parentId != null) {
            List<Object> records = new ArrayList<>();
            records.add(parentId);
            req.setRecords(records);
            ctrl.replies(req, res);
            return res;
        }

        if (folder == null) {
            return res;
        }

        switch (folder) {
        case "archive":
            ctrl.archived(req, res);
            return res;
        case "important":
            ctrl.important(req, res);
            return res;
        default:
            ctrl.inbox(req, res);
            return res;
        }
    }

    @GET
    @Path("{id}/followers")
    public Response messageFollowers(@PathParam("id") long id) {

        @SuppressWarnings("all")
        final Repository<?> repo = JpaRepository.of((Class) getResource().getModel());
        final Model entity = repo.find(id);
        final Response response = new Response();

        final Object all = followers.findFollowers(entity);

        response.setData(all);
        response.setStatus(Response.STATUS_SUCCESS);

        return response;
    }

    @POST
    @Path("{id}/follow")
    @SuppressWarnings("all")
    public Response messageFollow(@PathParam("id") long id, Request request) {

        final Repository<?> repo = JpaRepository.of((Class) getResource().getModel());
        final Model entity = repo.find(id);

        if (entity == null) {
            return messageFollowers(id);
        }
        if (request.getData() == null) {
            followers.follow(entity, AuthUtils.getUser());
            return messageFollowers(id);
        }

        final MailMessage message = Mapper.toBean(MailMessage.class, request.getData());
        if (message == null || message.getRecipients() == null || message.getRecipients().isEmpty()) {
            return messageFollowers(id);
        }

        for (MailAddress address : message.getRecipients()) {
            followers.follow(entity, address);
        }

        if (!StringUtils.isBlank(message.getBody())) {
            final MailService mailService = Beans.get(MailService.class);
            try {
                mailService.send(message);
            } catch (Exception e) {
                LOG.error("Unable to send email message to new followers", e);
            }
        }

        return messageFollowers(id);
    }

    @POST
    @Path("{id}/unfollow")
    @SuppressWarnings("all")
    public Response messageUnfollow(@PathParam("id") long id, Request request) {
        @SuppressWarnings("all")

        final Repository<?> repo = JpaRepository.of((Class) getResource().getModel());
        final Model entity = repo.find(id);
        if (entity == null) {
            return messageFollowers(id);
        }

        final List<Object> records = request.getRecords();
        if (records == null || records.isEmpty()) {
            followers.unfollow(entity, AuthUtils.getUser());
            return messageFollowers(id);
        }

        for (Object item : records) {
            final MailFollower follower = followers.find(Longs.tryParse(item.toString()));
            if (follower != null) {
                followers.unfollow(follower);
            }
        }

        return messageFollowers(id);
    }

    @POST
    @Path("{id}/message")
    public Response messagePost(@PathParam("id") long id, Request request) {

        final Response response = new Response();
        @SuppressWarnings("all")
        final Repository<?> repo = JpaRepository.of((Class) getResource().getModel());
        final Context ctx = Context.create(request.getData(), MailMessage.class);
        final MailMessage msg = ctx.asType(MailMessage.class);

        final Model entity = repo.find(id);
        final List<?> ids = (List<?>) request.getData().get("files");
        List<MetaFile> files = null;

        if (ids != null && ids.size() > 0) {
            final MetaFileRepository repoFiles = Beans.get(MetaFileRepository.class);
            files = repoFiles.all().filter("self.id IN :ids").bind("ids", ids).fetch();
        }

        MailMessage saved = messages.post(entity == null ? msg : entity, msg, files);
        Object item = messages.details(saved);

        response.setData(Lists.newArrayList(item));
        response.setStatus(Response.STATUS_SUCCESS);

        return response;
    }
}