/**
* Licensed to The Apereo Foundation under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
*
* The Apereo Foundation licenses this file to you under the Educational
* Community 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://opensource.org/licenses/ecl2.txt
*
* 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.
*
*/
package org.opencastproject.serviceregistry.remote;
import static org.apache.http.HttpStatus.SC_CONFLICT;
import static org.apache.http.HttpStatus.SC_CREATED;
import static org.apache.http.HttpStatus.SC_NOT_FOUND;
import static org.apache.http.HttpStatus.SC_OK;
import static org.opencastproject.util.IoSupport.closeAfterwards;
import org.opencastproject.job.api.Incident;
import org.opencastproject.job.api.Incident.Severity;
import org.opencastproject.job.api.IncidentParser;
import org.opencastproject.job.api.IncidentTree;
import org.opencastproject.job.api.JaxbJob;
import org.opencastproject.job.api.Job;
import org.opencastproject.job.api.JobParser;
import org.opencastproject.serviceregistry.api.IncidentL10n;
import org.opencastproject.serviceregistry.api.IncidentService;
import org.opencastproject.serviceregistry.api.IncidentServiceException;
import org.opencastproject.serviceregistry.api.RemoteBase;
import org.opencastproject.util.DateTimeSupport;
import org.opencastproject.util.NotFoundException;
import org.opencastproject.util.UrlSupport;
import org.opencastproject.util.data.Function;
import org.opencastproject.util.data.Tuple;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.message.BasicNameValuePair;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
/**
* A proxy to a remote incident service.
*/
public class IncidentServiceRemoteImpl extends RemoteBase implements IncidentService {
/** The logger */
private static final Logger logger = LoggerFactory.getLogger(IncidentServiceRemoteImpl.class);
private static final IncidentParser parser = IncidentParser.I;
public IncidentServiceRemoteImpl() {
super(JOB_TYPE);
}
@Override
@SuppressWarnings("unchecked")
public Incident storeIncident(Job job, Date timestamp, String code, Severity severity,
Map<String, String> descriptionParameters, List<Tuple<String, String>> details)
throws IncidentServiceException, IllegalStateException {
HttpPost post = new HttpPost();
try {
List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>();
params.add(new BasicNameValuePair("job", JobParser.toXml(new JaxbJob(job))));
params.add(new BasicNameValuePair("date", DateTimeSupport.toUTC(timestamp.getTime())));
params.add(new BasicNameValuePair("code", code));
params.add(new BasicNameValuePair("severity", severity.name()));
if (descriptionParameters != null)
params.add(new BasicNameValuePair("params", mapToString(descriptionParameters)));
if (details != null) {
JSONArray json = new JSONArray();
for (Tuple<String, String> detail : details) {
JSONObject jsTuple = new JSONObject();
jsTuple.put("title", detail.getA());
jsTuple.put("content", detail.getB());
json.add(jsTuple);
}
params.add(new BasicNameValuePair("details", json.toJSONString()));
}
post.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
} catch (Exception e) {
throw new IncidentServiceException("Unable to assemble a remote incident service request", e);
}
HttpResponse response = getResponse(post, SC_CREATED, SC_CONFLICT);
try {
if (response != null) {
if (response.getStatusLine().getStatusCode() == SC_CONFLICT) {
throw new IllegalStateException("No related job " + job.getId() + " of incident job found");
} else if (response.getStatusLine().getStatusCode() == SC_CREATED) {
Incident incident = parser.parseIncidentFromXml(response.getEntity().getContent()).toIncident();
logger.info("Incident '{}' created", incident.getId());
return incident;
}
}
} catch (Exception e) {
throw new IncidentServiceException(e);
} finally {
closeConnection(response);
}
throw new IncidentServiceException("Unable to store an incident of job " + job.getId());
}
@Override
public Incident getIncident(long incidentId) throws IncidentServiceException, NotFoundException {
final Function<InputStream, Incident> handler = new Function.X<InputStream, Incident>() {
@Override
public Incident xapply(InputStream in) throws IOException {
return parser.parseIncidentFromXml(in).toIncident();
}
};
return getIncidentXml(handler, incidentId);
}
public <A> A getIncidentXml(Function<InputStream, A> handler, long incidentId) throws IncidentServiceException,
NotFoundException {
final HttpGet get = new HttpGet(incidentId + ".xml");
final HttpResponse response = getResponse(get, SC_OK, SC_NOT_FOUND);
try {
if (response != null) {
if (SC_NOT_FOUND == response.getStatusLine().getStatusCode()) {
throw new NotFoundException("Incident from incident id " + incidentId
+ " not found in remote incident service!");
} else {
final A result = closeAfterwards(handler).apply(response.getEntity().getContent());
logger.debug("Successfully received incident from incident id {} from the remote incident service",
incidentId);
return result;
}
}
} catch (NotFoundException e) {
throw e;
} catch (Exception e) {
throw new IncidentServiceException("Unable to parse incident from remote incident service: " + e);
} finally {
closeConnection(response);
}
throw new IncidentServiceException("Unable to get incident from remote incident service");
}
@Override
public IncidentL10n getLocalization(long id, Locale locale) throws IncidentServiceException, NotFoundException {
HttpGet get = new HttpGet(UrlSupport.concat("localization", Long.toString(id)) + "?locale=" + locale.toString());
HttpResponse response = getResponse(get, SC_OK);
try {
if (response != null) {
if (SC_NOT_FOUND == response.getStatusLine().getStatusCode()) {
throw new NotFoundException("No localization found for the job incident with id " + id);
} else if (SC_OK == response.getStatusLine().getStatusCode()) {
String json = IOUtils.toString(response.getEntity().getContent(), "UTF-8");
final JSONObject obj = (JSONObject) JSONValue.parse(json);
logger.debug(
"Successfully received localization from job incident id {} with the locale {} from the remote incident service",
id, locale.toString());
return new IncidentL10n() {
@Override
public String getTitle() {
return (String) obj.get("title");
}
@Override
public String getDescription() {
return (String) obj.get("description");
}
};
}
}
} catch (Exception e) {
throw new IncidentServiceException("Unable to get localization of job incident from remote incident service: "
+ e);
} finally {
closeConnection(response);
}
throw new IncidentServiceException("Unable to get localization of job incident from remote incident service");
}
@Override
public IncidentTree getIncidentsOfJob(long jobId, boolean cascade) throws NotFoundException, IncidentServiceException {
final Function<InputStream, IncidentTree> handler = new Function.X<InputStream, IncidentTree>() {
@Override
public IncidentTree xapply(InputStream in) throws Exception {
return parser.parseIncidentTreeFromXml(in).toIncidentTree();
}
};
return getIncidentsOfJobXml(handler, jobId, cascade);
}
public <A> A getIncidentsOfJobXml(Function<InputStream, A> handler, long jobId, boolean cascade)
throws NotFoundException, IncidentServiceException {
HttpGet get = new HttpGet("job/" + jobId + ".xml?cascade=" + Boolean.toString(cascade) + "&format=sys");
HttpResponse response = getResponse(get, SC_OK, SC_NOT_FOUND);
try {
if (response != null) {
if (SC_NOT_FOUND == response.getStatusLine().getStatusCode()) {
throw new NotFoundException("Incident from job id " + jobId + " not found in remote incident service!");
} else {
final A result = closeAfterwards(handler).apply(response.getEntity().getContent());
logger.debug("Successfully received incident from job id {} from the remote incident service", jobId);
return result;
}
}
} catch (NotFoundException e) {
throw e;
} catch (Exception e) {
logger.error("Unable to parse incident form remote incident service", e);
throw new IncidentServiceException("Unable to parse incident from remote incident service: " + e);
} finally {
closeConnection(response);
}
throw new IncidentServiceException("Unable to get incident from remote incident service");
}
@Override
public List<Incident> getIncidentsOfJob(List<Long> jobIds) throws IncidentServiceException {
final Function<InputStream, List<Incident>> handler = new Function.X<InputStream, List<Incident>>() {
@Override
public List<Incident> xapply(InputStream in) throws Exception {
return parser.parseIncidentsFromXml(in).toIncidents();
}
};
return getIncidentsOfJobXml(handler, jobIds);
}
public <A> A getIncidentsOfJobXml(Function<InputStream, A> handler, List<Long> jobIds)
throws IncidentServiceException {
StringBuilder url = new StringBuilder("job/incidents.xml?format=sys");
if (!jobIds.isEmpty()) {
for (Long jobId : jobIds) {
url.append("&id=").append(jobId);
}
}
HttpGet get = new HttpGet(url.toString());
HttpResponse response = getResponse(get);
try {
if (response != null && SC_OK == response.getStatusLine().getStatusCode()) {
final A result = closeAfterwards(handler).apply(response.getEntity().getContent());
logger.debug("Successfully received incident summary from job ids {} from the remote incident service", jobIds);
return result;
}
} catch (Exception e) {
throw new IncidentServiceException("Unable to parse incidents from remote incident service: " + e);
} finally {
closeConnection(response);
}
throw new IncidentServiceException("Unable to get incidents from remote incident service");
}
/**
* Converts a Map<String, String> to s key=value\n string, suitable for the properties form parameter expected by the
* workflow rest endpoint.
*
* @param props
* The map of strings
* @return the string representation
*/
private String mapToString(Map<String, String> props) {
StringBuilder sb = new StringBuilder();
for (Entry<String, String> entry : props.entrySet()) {
sb.append(entry.getKey());
sb.append("=");
sb.append(entry.getValue());
sb.append("\n");
}
return sb.toString();
}
}