com.jaspersoft.jasperserver.jaxrs.job.JobsJaxrsService.java Source code

Java tutorial

Introduction

Here is the source code for com.jaspersoft.jasperserver.jaxrs.job.JobsJaxrsService.java

Source

/*
 * Copyright (C) 2005 - 2014 TIBCO Software Inc. All rights reserved.
 * http://www.jaspersoft.com.
 *
 * Unless you have purchased  a commercial license agreement from Jaspersoft,
 * the following license terms  apply:
 *
 * This program is free software: you can redistribute it and/or  modify
 * it under the terms of the GNU Affero General Public License  as
 * published by the Free Software Foundation, either version 3 of  the
 * License, or (at your option) any later version.
 *
 * 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.jaspersoft.jasperserver.jaxrs.job;

import com.jaspersoft.jasperserver.api.JSValidationException;
import com.jaspersoft.jasperserver.api.engine.scheduling.domain.ReportJob;
import com.jaspersoft.jasperserver.api.engine.scheduling.domain.ReportJobRuntimeInformation;
import com.jaspersoft.jasperserver.api.engine.scheduling.domain.ReportJobSummary;
import com.jaspersoft.jasperserver.api.engine.scheduling.domain.jaxb.JobSummariesListWrapper;
import com.jaspersoft.jasperserver.api.engine.scheduling.domain.reportjobmodel.ReportJobModel;
import com.jaspersoft.jasperserver.api.engine.scheduling.domain.reportjobmodel.ReportJobSourceModel;
import com.jaspersoft.jasperserver.dto.job.JobClientConstants;
import com.jaspersoft.jasperserver.remote.common.CallTemplate;
import com.jaspersoft.jasperserver.remote.common.RemoteServiceWrapper;
import com.jaspersoft.jasperserver.remote.exception.IllegalParameterValueException;
import com.jaspersoft.jasperserver.remote.exception.RemoteException;
import com.jaspersoft.jasperserver.remote.exception.ResourceNotFoundException;
import com.jaspersoft.jasperserver.remote.services.JobsService;
import com.jaspersoft.jasperserver.remote.services.impl.ReportJobCalendar;
import com.jaspersoft.jasperserver.war.cascade.CascadeResourceNotFoundException;
import com.jaspersoft.jasperserver.war.cascade.InputControlsLogicService;
import com.jaspersoft.jasperserver.war.cascade.InputControlsValidationException;
import net.sf.jasperreports.engine.JRParameter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
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.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;

/**
 * JAX-RS service "jobs" implementation
 *
 * @author Yaroslav.Kovalchyk
 * @version $Id: JobsJaxrsService.java 47331 2014-07-18 09:13:06Z kklein $
 */
@Component
@Scope("prototype")
@Path("/jobs")
@CallTemplate(JobsServiceCallTemplate.class)
public class JobsJaxrsService extends RemoteServiceWrapper<JobsService> {
    protected static final Log log = LogFactory.getLog(JobsJaxrsService.class);
    @javax.annotation.Resource
    private InputControlsLogicService inputControlsLogicService;
    @Context
    private HttpHeaders httpHeaders;

    @Resource(name = "jobsService")
    public void setRemoteService(JobsService remoteService) {
        this.remoteService = remoteService;
    }

    @DELETE
    @Path("/{id: \\d+}")
    public Response deleteJob(@PathParam("id") final long id) {
        return callRemoteService(new ConcreteCaller<Response>() {
            public Response call(JobsService service) throws RemoteException {
                service.deleteJob(id);
                return Response.ok("" + id).build();
            }
        });
    }

    @DELETE
    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    public Response deleteJobs(@QueryParam("id") final List<Long> ids) {
        return callRemoteService(new ConcreteCaller<Response>() {
            public Response call(JobsService service) throws RemoteException {
                long[] idsArray = new long[ids.size()];
                for (int i = 0; i < ids.size(); i++)
                    idsArray[i] = ids.get(i);
                service.deleteJobs(idsArray);
                return Response.ok(new JobIdListWrapper(ids)).build();
            }
        });
    }

    @GET
    @Path("/{id: \\d+}")
    // qs is specified to ensure, that application/xml is used if no Accept header specified in a request
    @Produces({ "application/json;qs=.5", "application/xml;qs=1" })
    public Response getJob(@PathParam("id") final long id) {
        return callRemoteService(new ConcreteCaller<Response>() {
            public Response call(JobsService service) throws RemoteException {
                ReportJob job = service.getJob(id);
                if (job != null)
                    return Response.ok(job).build();
                else
                    return Response.status(Response.Status.NOT_FOUND).build();
            }
        });
    }

    @GET
    @Path("/{id: \\d+}")
    @Produces(JobClientConstants.JOB_V_1_1_JSON_MEDIA_TYPE)
    public Response getJobWithProcessedParameters(@PathParam("id") final long id) {
        return callRemoteService(new ConcreteCaller<Response>() {
            public Response call(JobsService service) throws RemoteException {
                ReportJob job = service.getJob(id);
                if (job != null) {
                    return Response.ok(toClient(job)).build();
                } else {
                    return Response.status(Response.Status.NOT_FOUND).build();
                }
            }
        });
    }

    protected ReportJob toClient(ReportJob job) throws ResourceNotFoundException {
        String timeZone = job.getOutputTimeZone();
        if (job.getSource() != null && job.getSource().getParameters() != null
                && !job.getSource().getParameters().isEmpty()) {
            try {
                final Map<String, String[]> rawParameters = inputControlsLogicService
                        .formatTypedParameters(job.getSource().getReportUnitURI(), job.getSource().getParameters());
                // String[] is also Object. So, cast is safe.
                @SuppressWarnings("unchecked")
                Map<String, Object> castParameters = (Map) rawParameters;
                final List<String> acceptHeaderValues = httpHeaders.getRequestHeader(HttpHeaders.ACCEPT);
                if (acceptHeaderValues != null && !acceptHeaderValues.isEmpty()
                        && !JobClientConstants.JOB_V_1_1_JSON_MEDIA_TYPE.equals(acceptHeaderValues.get(0))) {
                    // JAXB XML marshaller can't handle String[] values, therefore convert String[] to Collection<String>
                    final Map<String, Object> xmlAdoptedParameters = new HashMap<String, Object>();
                    for (String currentParameter : rawParameters.keySet()) {
                        xmlAdoptedParameters.put(currentParameter,
                                new ArrayList<Object>(Arrays.asList(rawParameters.get(currentParameter))));
                    }
                    castParameters = xmlAdoptedParameters;
                }
                job.getSource().setParameters(castParameters);
            } catch (CascadeResourceNotFoundException e) {
                throw new ResourceNotFoundException("URI:" + e.getResourceUri() + " Type:" + e.getResourceType());
            } catch (InputControlsValidationException e) {
                throw new JSValidationException(e.getErrors());
            }
        }
        // restore timezone
        job.setOutputTimeZone(timeZone);
        // return instance of client extension, which handles marshalling of output time zone properly
        return new ReportJobClientExtension(job);
    }

    protected ReportJob toServer(ReportJob job) throws IllegalParameterValueException, ResourceNotFoundException {
        if (job.getSource() != null) {
            if (job.getSource().getParameters() == null) {
                job.getSource().setParameters(new HashMap<String, Object>());
            }
            final Map<String, Object> parameters = job.getSource().getParameters();
            // safe output time zone before input controls logic run
            final String outputTimeZone = job.getOutputTimeZone();
            try {
                // Parameters comes as Collection<String> but we need to have String[]. Convert them
                final Map<String, String[]> adoptedParameters = new HashMap<String, String[]>();
                for (String currentParameter : parameters.keySet()) {
                    if (parameters.get(currentParameter) instanceof Collection) {
                        // ClassCastException is properly processed below. If happens, then input format is incorrect
                        @SuppressWarnings("unchecked")
                        final Collection<String> collection = (Collection) parameters.get(currentParameter);
                        adoptedParameters.put(currentParameter, collection.toArray(new String[collection.size()]));
                    }
                }
                final Map<String, Object> typedParameters = inputControlsLogicService
                        .getTypedParameters(job.getSource().getReportUnitURI(), adoptedParameters);
                if (outputTimeZone != null) {
                    // restore output time zone
                    typedParameters.put(JRParameter.REPORT_TIME_ZONE, TimeZone.getTimeZone(outputTimeZone));
                }
                job.getSource().setParameters(typedParameters);
            } catch (ClassCastException e) {
                log.error(e);
                throw new IllegalParameterValueException("job.source.parameters", "Map with content of wrong type");
            } catch (InputControlsValidationException e) {
                throw new JSValidationException(e.getErrors());
            } catch (CascadeResourceNotFoundException e) {
                throw new ResourceNotFoundException("URI:" + e.getResourceUri() + " Type:" + e.getResourceType());
            }
        }
        return job;
    }

    @PUT
    @Produces(JobClientConstants.JOB_V_1_1_JSON_MEDIA_TYPE)
    @Consumes(JobClientConstants.JOB_V_1_1_JSON_MEDIA_TYPE)
    public Response scheduleJobWithProcessedParameters(final ReportJobClientExtension reportJob) {
        return callRemoteService(new ConcreteCaller<Response>() {
            public Response call(JobsService service) throws RemoteException {
                return Response.ok(toClient(service.scheduleJob(toServer(reportJob)))).build();
            }
        });
    }

    @PUT
    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    public Response scheduleJob(final ReportJob reportJob) {
        return callRemoteService(new ConcreteCaller<Response>() {
            public Response call(JobsService service) throws RemoteException {
                return Response.ok(service.scheduleJob(reportJob)).build();
            }
        });
    }

    @POST
    @Path("/{id: \\d+}")
    @Produces(JobClientConstants.JOB_V_1_1_JSON_MEDIA_TYPE)
    @Consumes(JobClientConstants.JOB_V_1_1_JSON_MEDIA_TYPE)
    public Response updateJobWithProcessedParameters(@PathParam("id") final long id,
            final ReportJobClientExtension reportJob) {
        return callRemoteService(new ConcreteCaller<Response>() {
            public Response call(JobsService service) throws RemoteException {
                if (id != reportJob.getId())
                    reportJob.setId(id);
                return Response.ok(toClient(service.updateJob(toServer(reportJob)))).build();
            }
        });
    }

    @POST
    @Path("/{id: \\d+}")
    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    public Response updateJob(@PathParam("id") final long id, final ReportJob reportJob) {
        return callRemoteService(new ConcreteCaller<Response>() {
            public Response call(JobsService service) throws RemoteException {
                if (id != reportJob.getId())
                    reportJob.setId(id);
                return Response.ok(service.updateJob(reportJob)).build();
            }
        });
    }

    /**
     * This method allows to update a collection of jobs in one call.
     *
     * @param jobIds                   - list of report job ID to update
     * @param jobModel                 - contain fields, which should be updated.
     * @param replaceTriggerIgnoreType - if true, then trigger need to be replaced (trigger type is ignored), else - trigger is updated.
     * @return empty response with status OK (code 200)
     */
    @POST
    @Produces(JobClientConstants.JOB_V_1_1_JSON_MEDIA_TYPE)
    @Consumes(JobClientConstants.JOB_V_1_1_JSON_MEDIA_TYPE)
    public Response updateJobsWithProcessedParameters(@QueryParam("id") final List<Long> jobIds,
            final ReportJobModel jobModel,
            @QueryParam("replaceTriggerIgnoreType") @DefaultValue("false") final Boolean replaceTriggerIgnoreType) {
        return callRemoteService(new ConcreteCaller<Response>() {
            public Response call(JobsService remoteService) throws RemoteException {
                remoteService.updateReportJobs(jobIds, (ReportJobModel) toServer(jobModel),
                        replaceTriggerIgnoreType);
                return Response.ok(new JobIdListWrapper(jobIds)).build();
            }
        });
    }

    /**
     * This method allows to update a collection of jobs in one call.
     *
     * @param jobIds                   - list of report job ID to update
     * @param jobModel                 - contain fields, which should be updated.
     * @param replaceTriggerIgnoreType - if true, then trigger need to be replaced (trigger type is ignored), else - trigger is updated.
     * @return empty response with status OK (code 200)
     */
    @POST
    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    public Response updateJobs(@QueryParam("id") final List<Long> jobIds, final ReportJobModel jobModel,
            @QueryParam("replaceTriggerIgnoreType") @DefaultValue("false") final Boolean replaceTriggerIgnoreType) {
        return callRemoteService(new ConcreteCaller<Response>() {
            public Response call(JobsService remoteService) throws RemoteException {
                remoteService.updateReportJobs(jobIds, jobModel, replaceTriggerIgnoreType);
                return Response.ok(new JobIdListWrapper(jobIds)).build();
            }
        });
    }

    /**
     * This method is used to get list of report job summary objects by given search criteria.
     * Fields of summary objects can be specified as separate parameters, all the other report job object's fields can be specified in example parameter (JSON string).
     * If some of summary fields specified in corresponding parameter and inside of example parameter, then value from example parameter is used for search.
     *
     * @param reportURI        - URI of the target report
     * @param owner            - report job creator user's name
     * @param jobName          - name of the report job
     * @param state            - runtime state of the report (defined but not implemented in current release)
     * @param previousFireTime - previous fire time of the report job (defined but not implemented in current release)
     * @param nextFireTime     - next fire time of the report job (defined but not implemented in current release)
     * @param exampleConverter - ReportJobModel in JSON format wrapped by JSON unmarshaller
     * @param startIndex       - block start index (pagination)
     * @param numberOfRows     - number of rows in a block (pagination)
     * @param sortType         - sorting column, possible values: NONE, SORTBY_JOBID, SORTBY_JOBNAME, SORTBY_REPORTURI, SORTBY_REPORTNAME,
     *                         SORTBY_REPORTFOLDER, SORTBY_OWNER, SORTBY_STATUS, SORTBY_LASTRUN, SORTBY_NEXTRUN
     * @param isAscending      - sorting direction, ascending if true
     * @return list of report job summaries
     */
    @GET
    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    public Response getReportJobs(@QueryParam("reportUnitURI") final String reportURI,
            @QueryParam("owner") final String owner, @QueryParam("label") final String jobName,
            @QueryParam("state") final String state, @QueryParam("previousFireTime") final Date previousFireTime,
            @QueryParam("nextFireTime") final Date nextFireTime,
            @QueryParam("example") final ReportJobModelJsonParam exampleConverter,
            @QueryParam("startIndex") final Integer startIndex,
            @QueryParam("numberOfRows") final Integer numberOfRows,
            @QueryParam("sortType") final ReportJobModel.ReportJobSortType sortType,
            @QueryParam("isAscending") final Boolean isAscending) {
        return callRemoteService(new ConcreteCaller<Response>() {
            public Response call(JobsService service) throws RemoteException {
                ReportJobModel criteriaObject = exampleConverter != null ? exampleConverter.getObject() : null;
                if (reportURI != null || owner != null || jobName != null || state != null
                        || previousFireTime != null || nextFireTime != null) {
                    if (criteriaObject == null)
                        criteriaObject = new ReportJobModel();
                    if (reportURI != null && (criteriaObject.getSourceModel() == null
                            || criteriaObject.getSourceModel().getReportUnitURI() == null)) {
                        if (criteriaObject.getSourceModel() == null)
                            criteriaObject.setSourceModel(new ReportJobSourceModel());
                        criteriaObject.getSourceModel().setReportUnitURI(reportURI);
                    }
                    if (owner != null && criteriaObject.getUsername() == null)
                        criteriaObject.setUsername(owner);
                    if (jobName != null && criteriaObject.getLabel() == null)
                        criteriaObject.setLabel(jobName);
                    if (state != null) {
                        //TODO state criteria
                    }
                    if (previousFireTime != null) {
                        //TODO previousFireTime criteria
                    }
                    if (nextFireTime != null) {
                        //TODO nextFireTime criteria
                    }
                }
                List<ReportJobSummary> result = service.getJobSummariesByExample(criteriaObject, startIndex,
                        numberOfRows, sortType, isAscending);
                return result != null && !result.isEmpty()
                        ? Response.ok(new JobSummariesListWrapper(result)).build()
                        : Response.status(Response.Status.NO_CONTENT).build();
            }
        });
    }

    @GET
    @Path("/{id: \\d+}/state")
    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    public Response getJobState(@PathParam("id") final long id) {
        return callRemoteService(new ConcreteCaller<Response>() {
            public Response call(JobsService service) throws RemoteException {
                ReportJobRuntimeInformation reportJobState = service.getReportJobState(id);
                if (reportJobState != null)
                    return Response.ok(reportJobState).build();
                else
                    return Response.status(Response.Status.NOT_FOUND).build();
            }
        });
    }

    /**
     * Pause currently scheduled jobs execution. Does not delete the jobs
     *
     * @param jobIdListWrapper - list of job ID to pause. Empty list means "pause all"
     * @return empty OK response
     */
    @POST
    @Path("/pause")
    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    public Response pause(final JobIdListWrapper jobIdListWrapper) {
        return callRemoteService(new ConcreteCaller<Response>() {
            public Response call(JobsService remoteService) throws RemoteException {
                remoteService.pause(jobIdListWrapper.getIds());
                return Response.ok(jobIdListWrapper).build();
            }
        });
    }

    /**
     * Resume currently scheduled jobs execution.
     *
     * @param jobIdListWrapper - list of job ID to pause. Empty list means "resume all"
     * @return empty OK response
     */
    @POST
    @Path("/resume")
    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    public Response resume(final JobIdListWrapper jobIdListWrapper) {
        return callRemoteService(new ConcreteCaller<Response>() {
            public Response call(JobsService remoteService) throws RemoteException {
                remoteService.resume(jobIdListWrapper.getIds());
                return Response.ok(jobIdListWrapper).build();
            }
        });
    }

    @POST
    @Path("/restart")
    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    public Response scheduleJobsOnceNow(final JobIdListWrapper jobIdListWrapper) {
        return callRemoteService(new ConcreteCaller<Response>() {
            public Response call(JobsService remoteService) throws RemoteException {
                remoteService.scheduleJobsOnceNow(jobIdListWrapper.getIds());
                return Response.ok(jobIdListWrapper).build();
            }
        });
    }

    @GET
    @Path("/calendars")
    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    public Response getCalendarNames(final @QueryParam("calendarType") String calendarType) {
        return callRemoteService(new ConcreteCaller<Response>() {
            public Response call(JobsService remoteService) throws RemoteException {
                ReportJobCalendar.Type type = null;
                if (calendarType != null) {
                    try {
                        type = ReportJobCalendar.Type.valueOf(calendarType);
                    } catch (IllegalArgumentException e) {
                        // just log. Given calendar type is invalid. Let's return empty response.
                        log.error(
                                "Unable to find corresponding calendar type enum item for '" + calendarType + "'.",
                                e);
                        return Response.status(Response.Status.NO_CONTENT).build();
                    }
                }
                final List<String> calendarNames = remoteService.getCalendarNames(type);
                if (calendarNames != null && !calendarNames.isEmpty())
                    return Response.ok(new CalendarNameListWrapper(calendarNames)).build();
                else
                    return Response.status(Response.Status.NO_CONTENT).build();
            }
        });
    }

    @GET
    @Path("/calendars/{calendarName}")
    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    public Response getCalendarByName(@PathParam("calendarName") final String calendarName) {
        return callRemoteService(new ConcreteCaller<Response>() {
            public Response call(JobsService remoteService) throws RemoteException {
                final ReportJobCalendar calendar = remoteService.getCalendar(calendarName);
                if (calendar != null)
                    return Response.ok(calendar).build();
                else
                    return Response.status(Response.Status.NOT_FOUND).build();
            }
        });
    }

    @DELETE
    @Path("/calendars/{calendarName}")
    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    public Response deleteCalendar(@PathParam("calendarName") final String calendarName) {
        return callRemoteService(new ConcreteCaller<Response>() {
            public Response call(JobsService remoteService) throws RemoteException {
                remoteService.deleteCalendar(calendarName);
                return Response.ok(calendarName).build();
            }
        });
    }

    @PUT
    @Path("/calendars/{calendarName}")
    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
    public Response putCalendar(@PathParam("calendarName") final String calendarName,
            final ReportJobCalendar calendar, @QueryParam("replace") @DefaultValue("false") final Boolean replace,
            @QueryParam("updateTriggers") @DefaultValue("false") final Boolean updateTriggers) {
        return callRemoteService(new ConcreteCaller<Response>() {
            public Response call(JobsService remoteService) throws RemoteException {
                remoteService.addCalendar(calendarName, calendar, replace, updateTriggers);
                return Response.ok(calendar).build();
            }
        });
    }

}