Java tutorial
package gov.bnl.channelfinder; /** * #%L * ChannelFinder Directory Service * %% * Copyright (C) 2010 - 2012 Helmholtz-Zentrum Berlin fr Materialien und Energie GmbH * %% * Copyright (C) 2010 - 2012 Brookhaven National Laboratory * All rights reserved. Use is subject to license terms. * #L% */ import static gov.bnl.channelfinder.ElasticSearchClient.getNewClient; import static org.elasticsearch.index.query.QueryBuilders.boolQuery; import static org.elasticsearch.index.query.QueryBuilders.disMaxQuery; import static org.elasticsearch.index.query.QueryBuilders.matchQuery; import static org.elasticsearch.index.query.QueryBuilders.nestedQuery; import static org.elasticsearch.index.query.QueryBuilders.wildcardQuery; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Map.Entry; import java.util.logging.Logger; import java.util.stream.Collectors; 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.WebApplicationException; import javax.ws.rs.core.Context; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.SecurityContext; import javax.ws.rs.core.StreamingOutput; import javax.ws.rs.core.UriInfo; import org.elasticsearch.action.bulk.BulkRequestBuilder; import org.elasticsearch.action.bulk.BulkResponse; import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.delete.DeleteResponse; import org.elasticsearch.action.get.GetResponse; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.action.update.UpdateResponse; import org.elasticsearch.client.Client; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.DisMaxQueryBuilder; import org.elasticsearch.index.query.MatchAllQueryBuilder; import org.elasticsearch.search.SearchHit; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.core.JsonEncoding; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.ObjectMapper; /** * Top level Jersey HTTP methods for the .../channels URL * * @author Kunal Shroff {@literal <shroffk@bnl.gov>}, Ralph Lange {@literal <ralph.lange@gmx.de>} */ @Path("/channels/") public class ChannelsResource { @Context private UriInfo uriInfo; @Context private SecurityContext securityContext; private Logger audit = Logger.getLogger(this.getClass().getPackage().getName() + ".audit"); private Logger log = Logger.getLogger(this.getClass().getName()); private final String chNameRegex = "[^\\s]+"; /** Creates a new instance of ChannelsResource */ public ChannelsResource() { } /** * GET method for retrieving a collection of Channel instances, * based on a multi-parameter query specifiying patterns for tags, property values, * and channel names to match against. * * @return HTTP Response */ @GET @Produces({ "application/json" }) public Response query() { StringBuffer performance = new StringBuffer(); long start = System.currentTimeMillis(); long totalStart = System.currentTimeMillis(); Client client = ElasticSearchClient.getSearchClient(); start = System.currentTimeMillis(); String user = securityContext.getUserPrincipal() != null ? securityContext.getUserPrincipal().getName() : ""; try { MultivaluedMap<String, String> parameters = uriInfo.getQueryParameters(); BoolQueryBuilder qb = boolQuery(); int size = 10000; for (Entry<String, List<String>> parameter : parameters.entrySet()) { switch (parameter.getKey()) { case "~name": for (String value : parameter.getValue()) { DisMaxQueryBuilder nameQuery = disMaxQuery(); for (String pattern : value.split("\\|")) { nameQuery.add(wildcardQuery("name", pattern.trim())); } qb.must(nameQuery); } break; case "~tag": for (String value : parameter.getValue()) { DisMaxQueryBuilder tagQuery = disMaxQuery(); for (String pattern : value.split("\\|")) { tagQuery.add(wildcardQuery("tags.name", pattern.trim())); } qb.must(nestedQuery("tags", tagQuery)); } break; case "~size": Optional<String> maxSize = parameter.getValue().stream().max((o1, o2) -> { return Integer.valueOf(o1).compareTo(Integer.valueOf(o2)); }); if (maxSize.isPresent()) { size = Integer.valueOf(maxSize.get()); } default: DisMaxQueryBuilder propertyQuery = disMaxQuery(); for (String value : parameter.getValue()) { for (String pattern : value.split("\\|")) { propertyQuery.add(nestedQuery("properties", boolQuery().must(matchQuery("properties.name", parameter.getKey().trim())) .must(wildcardQuery("properties.value", pattern.trim())))); } } qb.must(propertyQuery); break; } } performance.append("|prepare:" + (System.currentTimeMillis() - start)); start = System.currentTimeMillis(); final SearchResponse qbResult = client.prepareSearch("channelfinder").setQuery(qb).setSize(size) .execute().actionGet(); performance.append( "|query:(" + qbResult.getHits().getTotalHits() + ")" + (System.currentTimeMillis() - start)); start = System.currentTimeMillis(); final ObjectMapper mapper = new ObjectMapper(); mapper.addMixIn(XmlProperty.class, OnlyXmlProperty.class); mapper.addMixIn(XmlTag.class, OnlyXmlTag.class); start = System.currentTimeMillis(); StreamingOutput stream = new StreamingOutput() { @Override public void write(OutputStream os) throws IOException, WebApplicationException { JsonGenerator jg = mapper.getFactory().createGenerator(os, JsonEncoding.UTF8); jg.writeStartArray(); if (qbResult != null) { for (SearchHit hit : qbResult.getHits()) { jg.writeObject(mapper.readValue(hit.source(), XmlChannel.class)); jg.flush(); } } jg.writeEndArray(); jg.flush(); jg.close(); } }; performance.append("|parse:" + (System.currentTimeMillis() - start)); Response r = Response.ok(stream).build(); log.info(user + "|" + uriInfo.getPath() + "|GET|OK" + performance.toString() + "|total:" + (System.currentTimeMillis() - totalStart) + "|" + r.getStatus() + "|returns " + qbResult.getHits().getTotalHits() + " channels"); // log.info( qbResult.getHits().getTotalHits() + " " +(System.currentTimeMillis() - totalStart)); return r; } catch (Exception e) { return handleException(user, "GET", Response.Status.INTERNAL_SERVER_ERROR, e); } finally { } } /** * PUT method for creating multiple channel instances. * * @param data XmlChannels data (from payload) * @return HTTP Response * @throws IOException when audit or log fail */ @PUT @Consumes({ "application/json" }) public Response create(List<XmlChannel> data) throws IOException { Client client = getNewClient(); UserManager um = UserManager.getInstance(); um.setUser(securityContext.getUserPrincipal(), securityContext.isUserInRole("Administrator")); ObjectMapper mapper = new ObjectMapper(); try { long start = System.currentTimeMillis(); data = validateChannels(data, client); audit.info(um.getUserName() + "|" + uriInfo.getPath() + "|PUT|validation : " + (System.currentTimeMillis() - start)); start = System.currentTimeMillis(); BulkRequestBuilder bulkRequest = client.prepareBulk(); for (XmlChannel channel : data) { bulkRequest.add(client.prepareUpdate("channelfinder", "channel", channel.getName()) .setDoc(mapper.writeValueAsBytes(channel)) .setUpsert(new IndexRequest("channelfinder", "channel", channel.getName()) .source(mapper.writeValueAsBytes(channel)))); } String prepare = "|Prepare: " + (System.currentTimeMillis() - start) + "|"; start = System.currentTimeMillis(); bulkRequest.setRefresh(true); BulkResponse bulkResponse = bulkRequest.execute().actionGet(); String execute = "|Execute: " + (System.currentTimeMillis() - start) + "|"; start = System.currentTimeMillis(); if (bulkResponse.hasFailures()) { audit.severe(bulkResponse.buildFailureMessage()); throw new Exception(); } else { Response r = Response.noContent().build(); audit.info(um.getUserName() + "|" + uriInfo.getPath() + "|POST|OK|" + r.getStatus() + prepare + execute + "|data=" + (data)); return r; } } catch (IllegalArgumentException e) { return handleException(um.getUserName(), "PUT", Response.Status.BAD_REQUEST, e); } catch (Exception e) { return handleException(um.getUserName(), "PUT", Response.Status.INTERNAL_SERVER_ERROR, e); } finally { client.close(); } } /** * GET method for retrieving an instance of Channel identified by <tt>chan</tt>. * * @param chan channel name * @return HTTP Response */ @GET @Path("{chName: " + chNameRegex + "}") @Produces({ "application/json" }) public Response read(@PathParam("chName") String chan) { audit.info("getting ch:" + chan); Client client = ElasticSearchClient.getSearchClient(); String user = securityContext.getUserPrincipal() != null ? securityContext.getUserPrincipal().getName() : ""; try { final GetResponse response = client.prepareGet("channelfinder", "channel", chan).execute().actionGet(); Response r; if (response.isExists()) { final ObjectMapper mapper = new ObjectMapper(); mapper.addMixIn(XmlProperty.class, OnlyXmlProperty.class); mapper.addMixIn(XmlTag.class, OnlyXmlTag.class); StreamingOutput stream = new StreamingOutput() { @Override public void write(OutputStream os) throws IOException, WebApplicationException { JsonGenerator jg = mapper.getFactory().createGenerator(os, JsonEncoding.UTF8); jg.writeObject(mapper.readValue(response.getSourceAsBytes(), XmlChannel.class)); jg.flush(); jg.close(); } }; r = Response.ok(stream).build(); } else { r = Response.status(Response.Status.NOT_FOUND).build(); } log.fine(user + "|" + uriInfo.getPath() + "|GET|OK|" + r.getStatus()); return r; } catch (Exception e) { return handleException(user, "GET", Response.Status.INTERNAL_SERVER_ERROR, e); } finally { } } /** * PUT method for creating/replacing a channel instance identified by the payload. * The <b>complete</b> set of properties for the channel must be supplied, * which will replace the existing set of properties. * * @param chan name of channel to create or add * @param data new data (properties/tags) for channel <tt>chan</tt> * @return HTTP response */ @PUT @Path("{chName: " + chNameRegex + "}") @Consumes("application/json") public Response create(@PathParam("chName") String chan, XmlChannel data) { audit.severe("PUT:" + XmlChannel.toLog(data)); UserManager um = UserManager.getInstance(); um.setUser(securityContext.getUserPrincipal(), securityContext.isUserInRole("Administrator")); if (data.getName() == null || data.getName().isEmpty()) { return handleException(um.getUserName(), "PUT", Response.Status.BAD_REQUEST, "Specified channel name '" + chan + "' and payload channel name '" + data.getName() + "' do not match"); } if (!validateChannelName(chan, data)) { return handleException(um.getUserName(), "PUT", Response.Status.BAD_REQUEST, "Specified channel name '" + chan + "' and payload channel name '" + data.getName() + "' do not match"); } long start = System.currentTimeMillis(); Client client = getNewClient(); ObjectMapper mapper = new ObjectMapper(); try { start = System.currentTimeMillis(); data = validateChannel(data, client); audit.info(um.getUserName() + "|" + uriInfo.getPath() + "|PUT|validation : " + (System.currentTimeMillis() - start)); IndexRequest indexRequest = new IndexRequest("channelfinder", "channel", chan) .source(mapper.writeValueAsBytes(data)); UpdateRequest updateRequest = new UpdateRequest("channelfinder", "channel", chan) .doc(mapper.writeValueAsBytes(data)).upsert(indexRequest).refresh(true); UpdateResponse result = client.update(updateRequest).actionGet(); Response r = Response.noContent().build(); audit.info(um.getUserName() + "|" + uriInfo.getPath() + "|PUT|OK|" + r.getStatus() + "|data=" + XmlChannel.toLog(data)); return r; } catch (IllegalArgumentException e) { return handleException(um.getUserName(), "PUT", Response.Status.BAD_REQUEST, e); } catch (Exception e) { return handleException(um.getUserName(), "PUT", Response.Status.INTERNAL_SERVER_ERROR, e); } finally { client.close(); } } /** * POST method for merging properties and tags of the Channel identified by the * payload into an existing channel. * * @param chan name of channel to add * @param data new XmlChannel data (properties/tags) to be merged into channel <tt>chan</tt> * @return HTTP response */ @POST @Path("{chName: " + chNameRegex + "}") @Consumes({ "application/xml", "application/json" }) public Response update(@PathParam("chName") String chan, XmlChannel data) { long start = System.currentTimeMillis(); UserManager um = UserManager.getInstance(); um.setUser(securityContext.getUserPrincipal(), securityContext.isUserInRole("Administrator")); Client client = getNewClient(); if (data.getName() == null || data.getName().isEmpty()) { return handleException(um.getUserName(), "PUT", Response.Status.BAD_REQUEST, "Specified channel name '" + chan + "' and payload channel name '" + data.getName() + "' do not match"); } if (!validateChannelName(chan, data)) { return renameChannel(um, client, chan, data); } ObjectMapper mapper = new ObjectMapper(); try { start = System.currentTimeMillis(); data = validateChannel(data, client); audit.info(um.getUserName() + "|" + uriInfo.getPath() + "|POST|validation : " + (System.currentTimeMillis() - start)); start = System.currentTimeMillis(); GetResponse response = client.prepareGet("channelfinder", "channel", chan).execute().actionGet(); if (response.isExists()) { XmlChannel channel = mapper.readValue(response.getSourceAsBytes(), XmlChannel.class); channel.setName(data.getName()); channel.setOwner(data.getOwner()); Collection<String> propNames = ChannelUtil.getPropertyNames(data); data.getProperties().addAll(channel.getProperties().stream().filter(p -> { return !propNames.contains(p.getName()); }).collect(Collectors.toList())); channel.setProperties(data.getProperties()); Collection<String> tagNames = ChannelUtil.getTagNames(data); data.getTags().addAll(channel.getTags().stream().filter(t -> { return !tagNames.contains(t.getName()); }).collect(Collectors.toList())); channel.setTags(data.getTags()); UpdateRequest updateRequest = new UpdateRequest("channelfinder", "channel", chan) .doc(mapper.writeValueAsBytes(channel)).refresh(true); audit.info(um.getUserName() + "|" + uriInfo.getPath() + "|POST|prepare : " + (System.currentTimeMillis() - start)); start = System.currentTimeMillis(); UpdateResponse result = client.update(updateRequest).actionGet(); Response r = Response.noContent().build(); audit.info(um.getUserName() + "|" + uriInfo.getPath() + "|POST|OK|" + r.getStatus() + "|data=" + XmlChannel.toLog(data)); return r; } else { return handleException(um.getUserName(), "POST", Response.Status.NOT_FOUND, "Specified channel '" + chan + "' does not exist"); } } catch (IllegalArgumentException e) { return handleException(um.getUserName(), "POST", Response.Status.BAD_REQUEST, e); } catch (Exception e) { return handleException(um.getUserName(), "POST", Response.Status.INTERNAL_SERVER_ERROR, e); } finally { client.close(); } } private Response renameChannel(UserManager um, Client client, String chan, XmlChannel data) { GetResponse response = client.prepareGet("channelfinder", "channel", chan).execute().actionGet(); if (!response.isExists()) { handleException(um.getUserName(), "POST", Response.Status.NOT_FOUND, "Specified channel '" + chan + "' does not exist"); } ObjectMapper mapper = new ObjectMapper(); try { XmlChannel originalChannel = mapper.readValue(response.getSourceAsBytes(), XmlChannel.class); originalChannel.setName(data.getName()); Collection<String> propNames = ChannelUtil.getPropertyNames(data); data.getProperties().addAll(originalChannel.getProperties().stream().filter(p -> { return !propNames.contains(p.getName()); }).collect(Collectors.toList())); originalChannel.setProperties(data.getProperties()); Collection<String> tagNames = ChannelUtil.getTagNames(data); data.getTags().addAll(originalChannel.getTags().stream().filter(t -> { return !tagNames.contains(t.getName()); }).collect(Collectors.toList())); originalChannel.setTags(data.getTags()); BulkRequestBuilder bulkRequest = client.prepareBulk(); bulkRequest.add(new DeleteRequest("channelfinder", "channel", chan)); IndexRequest indexRequest = new IndexRequest("channelfinder", "channel", originalChannel.getName()) .source(mapper.writeValueAsBytes(originalChannel)); bulkRequest.add(indexRequest); bulkRequest.setRefresh(true); BulkResponse bulkResponse = bulkRequest.execute().actionGet(); if (bulkResponse.hasFailures()) { audit.severe(bulkResponse.buildFailureMessage()); if (bulkResponse.buildFailureMessage().contains("DocumentMissingException")) { return handleException(um.getUserName(), "POST", Response.Status.NOT_FOUND, bulkResponse.buildFailureMessage()); } else { return handleException(um.getUserName(), "POST", Response.Status.INTERNAL_SERVER_ERROR, bulkResponse.buildFailureMessage()); } } else { Response r = Response.ok(originalChannel).build(); audit.info(um.getUserName() + "|" + uriInfo.getPath() + "|POST|OK|" + r.getStatus() + "|data="); return r; } } catch (IOException e) { return handleException(um.getUserName(), "POST", Response.Status.INTERNAL_SERVER_ERROR, e); } } private boolean validateChannelName(String chan, XmlChannel data) { return chan.equals(data.getName()); } /** * DELETE method for deleting a channel instance identified by * path parameter <tt>chan</tt>. * * @param chan channel to remove * @return HTTP Response */ @DELETE @Path("{chName: " + chNameRegex + "}") public Response remove(@PathParam("chName") String chan) { audit.info("deleting ch:" + chan); Client client = getNewClient(); UserManager um = UserManager.getInstance(); um.setUser(securityContext.getUserPrincipal(), securityContext.isUserInRole("Administrator")); try { DeleteResponse response = client.prepareDelete("channelfinder", "channel", chan).setRefresh(true) .execute().get(); if (response.isFound()) { Response r = Response.ok().build(); audit.info(um.getUserName() + "|" + uriInfo.getPath() + "|DELETE|OK|" + r.getStatus()); return r; } else { return Response.status(Status.NOT_FOUND).entity("Specified channel '" + chan + "' does not exist") .build(); } } catch (Exception e) { return handleException(um.getUserName(), "DELETE", Response.Status.INTERNAL_SERVER_ERROR, e); } finally { client.close(); } } /** * Check is all the tags and properties already exist * @return * @throws IOException * @throws JsonMappingException * @throws JsonParseException */ private List<XmlChannel> validateChannels(List<XmlChannel> channels, Client client) throws JsonParseException, JsonMappingException, IOException { for (XmlChannel channel : channels) { if (channel.getName() == null || channel.getName().isEmpty()) { throw new IllegalArgumentException("Invalid channel name "); } if (channel.getOwner() == null || channel.getOwner().isEmpty()) { throw new IllegalArgumentException( "Invalid channel owner (null or empty string) for '" + channel.getName() + "'"); } for (XmlProperty xmlProperty : channel.getProperties()) { if (xmlProperty.getValue() == null || xmlProperty.getValue().isEmpty()) { throw new IllegalArgumentException( "Invalid property value (missing or null or empty string) for '" + xmlProperty.getName() + "'"); } } } final Map<String, XmlTag> tags = new HashMap<String, XmlTag>(); final Map<String, XmlProperty> properties = new HashMap<String, XmlProperty>(); ObjectMapper mapper = new ObjectMapper(); mapper.addMixIn(XmlProperty.class, OnlyXmlProperty.class); mapper.addMixIn(XmlTag.class, OnlyXmlTag.class); SearchResponse response = client.prepareSearch("properties").setTypes("property") .setQuery(new MatchAllQueryBuilder()).setSize(1000).execute().actionGet(); for (SearchHit hit : response.getHits()) { XmlProperty prop = mapper.readValue(hit.getSourceAsString(), XmlProperty.class); properties.put(prop.getName(), prop); } response = client.prepareSearch("tags").setTypes("tag").setQuery(new MatchAllQueryBuilder()).setSize(1000) .execute().actionGet(); for (SearchHit hit : response.getHits()) { XmlTag tag = mapper.readValue(hit.getSourceAsString(), XmlTag.class); tags.put(tag.getName(), tag); } if (tags.keySet().containsAll(ChannelUtil.getTagNames(channels)) && properties.keySet().containsAll(ChannelUtil.getPropertyNames(channels))) { for (XmlChannel channel : channels) { channel.getTags().parallelStream().forEach((tag) -> { tag.setOwner(tags.get(tag.getName()).getOwner()); }); channel.getProperties().parallelStream().forEach((prop) -> { prop.setOwner(properties.get(prop.getName()).getOwner()); }); } return channels; } else { StringBuffer errorMsg = new StringBuffer(); Collection<String> missingTags = ChannelUtil.getTagNames(channels); missingTags.removeAll(tags.keySet()); for (String tag : missingTags) { errorMsg.append(tag + "|"); } Collection<String> missingProps = ChannelUtil.getPropertyNames(channels); missingProps.removeAll(properties.keySet()); for (String prop : missingProps) { errorMsg.append(prop + "|"); } throw new IllegalArgumentException( "The following Tags and/or Properties on the channel don't exist -- " + errorMsg.toString()); } } /** * Check is all the tags and properties already exist * @return * @throws IOException * @throws JsonMappingException * @throws JsonParseException */ private XmlChannel validateChannel(XmlChannel channel, Client client) throws JsonParseException, JsonMappingException, IOException { if (channel.getName() == null || channel.getName().isEmpty()) { throw new IllegalArgumentException("Invalid channel name "); } if (channel.getOwner() == null || channel.getOwner().isEmpty()) { throw new IllegalArgumentException( "Invalid channel owner (null or empty string) for '" + channel.getName() + "'"); } for (XmlProperty xmlProperty : channel.getProperties()) { if (xmlProperty.getValue() == null || xmlProperty.getValue().isEmpty()) { throw new IllegalArgumentException("Invalid property value (missing or null or empty string) for '" + xmlProperty.getName() + "'"); } } final Map<String, XmlTag> tags = new HashMap<String, XmlTag>(); final Map<String, XmlProperty> properties = new HashMap<String, XmlProperty>(); ObjectMapper mapper = new ObjectMapper(); mapper.addMixIn(XmlProperty.class, OnlyXmlProperty.class); mapper.addMixIn(XmlTag.class, OnlyXmlTag.class); SearchResponse response = client.prepareSearch("properties").setTypes("property") .setQuery(new MatchAllQueryBuilder()).setSize(1000).execute().actionGet(); for (SearchHit hit : response.getHits()) { XmlProperty prop = mapper.readValue(hit.getSourceAsString(), XmlProperty.class); properties.put(prop.getName(), prop); } response = client.prepareSearch("tags").setTypes("tag").setQuery(new MatchAllQueryBuilder()).setSize(1000) .execute().actionGet(); for (SearchHit hit : response.getHits()) { XmlTag tag = mapper.readValue(hit.getSourceAsString(), XmlTag.class); tags.put(tag.getName(), tag); } if (tags.keySet().containsAll(ChannelUtil.getTagNames(channel)) && properties.keySet().containsAll(ChannelUtil.getPropertyNames(channel))) { channel.getTags().parallelStream().forEach((tag) -> { tag.setOwner(tags.get(tag.getName()).getOwner()); }); channel.getProperties().parallelStream().forEach((prop) -> { prop.setOwner(properties.get(prop.getName()).getOwner()); }); return channel; } else { StringBuffer errorMsg = new StringBuffer(); Collection<String> missingTags = ChannelUtil.getTagNames(channel); missingTags.removeAll(tags.keySet()); for (String tag : missingTags) { errorMsg.append(tag + "|"); } Collection<String> missingProps = ChannelUtil.getPropertyNames(channel); missingProps.removeAll(properties.keySet()); for (String prop : missingProps) { errorMsg.append(prop + "|"); } throw new IllegalArgumentException( "The following Tags and/or Properties on the channel don't exist -- " + errorMsg.toString()); } } private Response handleException(String user, String requestType, Response.Status status, Exception e) { return handleException(user, requestType, status, e.getMessage()); } private Response handleException(String user, String requestType, Response.Status status, String message) { log.warning(user + "|" + uriInfo.getPath() + "|" + requestType + "|ERROR|" + status + "|cause=" + message); return new CFException(status, message).toResponse(); } abstract class OnlyXmlProperty { @JsonIgnore private List<XmlChannel> channels; } abstract class OnlyXmlTag { @JsonIgnore private List<XmlChannel> channels; } }