Date: Thu, 28 Mar 2024 10:27:38 +0000 (UTC) Message-ID: <718326398.49.1711621658964@77bbbbcf2a9b> Subject: Exported From Confluence MIME-Version: 1.0 Content-Type: multipart/related; boundary="----=_Part_48_493362969.1711621658963" ------=_Part_48_493362969.1711621658963 Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: quoted-printable Content-Location: file:///C:/exported.html
Outdated Code
In Stripes 1.7 there is a much better solution to all of this using
I used Stripes 1.5 to communicate with an angular 1.5 client which posts= with a JSON body by wrapping the HTTPServletRequest.
It uses Jackson to read the request body and stores each JSON property a= s an HTTP request param, with the property's value as a JSON string.
This allowed us to again use Jackson in our ActionBeans to populate our = model objects.
var params =3D { startDate: asIso8601String(new Date(2017, 0, 1)), endDate: asIso8601String(new Date(2018, 0, 1)) } $http.post(restAPI.stats, { "params": params });
package com.example.util; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.springframework.http.MediaType; import com.example.util.JsonRequest; import net.sourceforge.stripes.action.ActionBeanContext; import net.sourceforge.stripes.action.Resolution; import net.sourceforge.stripes.controller.ExecutionContext; import net.sourceforge.stripes.controller.Interceptor; import net.sourceforge.stripes.controller.Intercepts; import net.sourceforge.stripes.controller.LifecycleStage; /** * Wrap JSON requests in this wrapper so that Strings can access the JSON c= ontents via request parameters. */ @Intercepts(LifecycleStage.ActionBeanResolution) public class JsonRequestInterceptor implements Interceptor { @Override public Resolution intercept(ExecutionContext executionCtx) throws Excepti= on { ActionBeanContext ctx =3D executionCtx.getActionBeanContext(); HttpServletRequest request =3D ctx.getRequest(); String contentType =3D request.getHeader("content-type"); if (StringUtils.contains(contentType, MediaType.APPLICATION_JSON.toStri= ng())) { // wrap the request ctx.setRequest(new JsonRequest(ctx.getRequest())); } return executionCtx.proceed(); } }
package com.example.util; import java.io.BufferedReader; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import org.apache.commons.lang3.ArrayUtils; import org.springframework.http.MediaType; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; /** * Used to wrap an {@link HttpServletRequest} with a {@link MediaType#APPLI= CATION_JSON} header so that the body of the request can be read as a JSON o= bject and so that Stripes can still have access to * request parameters. */ public class JsonRequest extends HttpServletRequestWrapper { private Map<String, String[]> parameters =3D new HashMap<String,= String[]>(); public JsonRequest(HttpServletRequest request) throws Exception { super(request); buildParameters(request); } /** * Read the body of the request as JSON then map the root field names as = parameter keys. */ private void buildParameters(HttpServletRequest request) throws Exception= { StringBuilder builder =3D new StringBuilder(); String line; BufferedReader reader =3D request.getReader(); while ((line =3D reader.readLine()) !=3D null) { builder.append(line); } String body =3D builder.toString(); ObjectMapper mapper =3D new ObjectMapper(); JsonNode root =3D mapper.readValue(body, JsonNode.class); Iterator<String> fieldNames =3D root.fieldNames(); while (fieldNames.hasNext()) { String key =3D fieldNames.next(); JsonNode value =3D root.get(key); parameters.put(key, new String[] { value.toString() }); } } @Override public String getParameter(String name) { String value =3D null; Map<String, String[]> mergedParams =3D getParameterMap(); String[] values =3D mergedParams.get(name); if (ArrayUtils.getLength(values) > 0) { value =3D values[0]; } return value; } @Override public Map<String, String[]> getParameterMap() { Map<String, String[]> parentParameters =3D super.getParameterMap(= ); Map<String, String[]> mergedParams =3D new HashMap<String, Str= ing[]>(parentParameters.size() + parameters.size()); mergedParams.putAll(parentParameters); mergedParams.putAll(parameters); return mergedParams; } @Override public String[] getParameterValues(String name) { Map<String, String[]> mergedParams =3D getParameterMap(); return mergedParams.get(name); } }
package com.example.stats; import java.util.Date; import org.apache.commons.lang3.StringUtils; import com.example.stats.StatsService; import com.example.util.JsonTypeConverter; import com.example.util.BaseWS; import com.example.util.JsonResolution; import net.sourceforge.stripes.action.DefaultHandler; import net.sourceforge.stripes.action.Resolution; import net.sourceforge.stripes.action.UrlBinding; import net.sourceforge.stripes.integration.spring.SpringBean; import net.sourceforge.stripes.validation.SimpleError; import net.sourceforge.stripes.validation.Validate; import net.sourceforge.stripes.validation.ValidationErrors; import net.sourceforge.stripes.validation.ValidationMethod; /** * Stats web service. *=20 * Loads global stats, and if a date range is provided loads the interval s= tats as well. */ @UrlBinding("/ws/stats") public class StatsWS extends BaseWS { private String START_DATE_FIELD_NAME =3D "params.startDate"; private String END_DATE_FIELD_NAME =3D "params.endDate"; @SpringBean private StatsService statsService; private StatsParamsRequest params; private Date startDate; // populated during validation private Date endDate; // populated during validation @Validate(required =3D true, converter =3D JsonTypeConverter.class) public void setParams(StatsParamsRequest params) { this.params =3D params; } @DefaultHandler public Resolution reply() { Stats global =3D statsService.loadGlobalStats(); Stats interval =3D null; if (startDate !=3D null && endDate !=3D null) { interval =3D statsService.loadIntervalStats(startDate, endDate); } StatsReply reply =3D new StatsReply(); reply.setGlobal(global); reply.setInterval(interval); return new JsonResolution(reply); } @ValidationMethod public void validateDates(ValidationErrors errors) { if (params =3D=3D null) { errors.add("params", new SimpleError("params is null")); } boolean hasStart =3D !StringUtils.isEmpty(params.getStartDate()); boolean hasEnd =3D !StringUtils.isEmpty(params.getEndDate()); if (hasStart && hasEnd) { startDate =3D parseIso8601Date(params.getStartDate(), START_DATE_FIEL= D_NAME, errors); endDate =3D parseIso8601Date(params.getEndDate(), END_DATE_FIELD_NAME= , errors); } else if (hasStart ^ hasEnd) { // both dates can be null, but not just o= ne of them errors.add(START_DATE_FIELD_NAME, new SimpleError(StatsService.MSG_ST= ART_END_DATES)); } } }
package com.example.util; import java.io.IOException; import java.util.Collection; import java.util.Locale; import java.util.logging.Level; import java.util.logging.Logger; import com.fasterxml.jackson.databind.ObjectMapper; import net.sourceforge.stripes.validation.TypeConverter; @SuppressWarnings("rawtypes") public class JsonTypeConverter implements TypeConverter { @Override public void setLocale(Locale locale) { // nothing to do } @SuppressWarnings("unchecked") @Override public Object convert(String string, Class type, Collection clctn) { ObjectMapper mapper =3D new ObjectMapper(); try { return mapper.readValue(string, type); } catch (IOException ex) { Logger.getLogger(JsonTypeConverter.class.getName()).log(Level.SEVERE,= null, ex); } return null; } }
package com.example.util; import java.io.IOException; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.Validate; import org.apache.commons.logging.LogFactory; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.ser.FilterProvider; import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; import com.example.util.LogUtil; import net.sourceforge.stripes.action.StreamingResolution; /** * The default response to a web service which {@link JsonReply replys} wit= h {@link StreamingResolution streaming} JSON using a Jackson {@link ObjectM= apper}. */ public class JsonResolution extends AbstractJsonResolution { private static final LogUtil LOG =3D LogUtil.getInstance(LogFactory.getLo= g(JsonResolution.class)); private final JsonReply reply; public JsonResolution(JsonReply reply) { super(); Validate.notNull(reply); this.reply =3D reply; } @Override public void stream(HttpServletResponse response) { Validate.notNull(response); LOG.trace("Replying with: ", reply.getClass().getSimpleName(), " : ", r= eply); ObjectMapper mapper =3D new ObjectMapper(); if (reply instanceof EmptyReply) { mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); } try { if (reply instanceof FilterableJsonReply) { // ignore specific fields String[] nonFilteredFields =3D ((FilterableJsonReply) reply).getNon= FilteredFields(); FilterProvider filters =3D new SimpleFilterProvider().addFilter("fi= lter", SimpleBeanPropertyFilter.filterOutAllExcept(nonFilteredFields)); mapper.writer(filters).writeValue(response.getOutputStream(), reply= ); } else { // write full content mapper.writeValue(response.getOutputStream(), reply); } } catch (IOException e) { LOG.error(e); } } }
package com.example.util; import javax.servlet.http.HttpServletResponse; import net.sourceforge.stripes.action.StreamingResolution; /** * An abstract base {@link StreamingResolution streaming} JSON response to = a web service. */ public abstract class AbstractJsonResolution extends StreamingResolution { protected static final String DEFAULT_CHARSET =3D "UTF-8"; protected static final String JSON_MEDIA_TYPE =3D "application/json"; protected AbstractJsonResolution() { super(JSON_MEDIA_TYPE); setCharacterEncoding(DEFAULT_CHARSET); } @Override protected abstract void stream(HttpServletResponse response); }