b51d7598 by Ean Schuessler

Remove duplicate listProducts service and add WebFacadeStub for MCP integration

- Removed duplicate listProducts service from McpServices.xml as it was causing conflicts
- Added WebFacadeStub.groovy to support web functionality in MCP context
- MCP server analysis complete: identified authorization issues with mcp-business user
- Found that mcp-business lacks proper entity permissions (e.g., mantle.shipment.ShipmentParty)
- Need to implement role-based access control for proper MCP business functionality
- Foundation is solid - 1,200+ services and screens exposed, but authorization layer needed
1 parent f3884d36
...@@ -982,48 +982,6 @@ try { ...@@ -982,48 +982,6 @@ try {
982 982
983 983
984 984
985 <service verb="list" noun="Products" authenticate="true" allow-remote="true" transaction-timeout="30">
986 <description>List products with basic information for MCP business toolkit</description>
987 <in-parameters>
988 <parameter name="productCategoryId" required="false"/>
989 <parameter name="ownerPartyId" required="false"/>
990 <parameter name="pageSize" type="Integer" default="20"/>
991 <parameter name="pageIndex" type="Integer" default="0"/>
992 </in-parameters>
993 <out-parameters>
994 <parameter name="products" type="List"/>
995 <parameter name="totalCount" type="Integer"/>
996 </out-parameters>
997 <actions>
998 <script><![CDATA[
999 import org.moqui.context.ExecutionContext
1000 import org.moqui.impl.context.UserFacadeImpl.UserInfo
1001
1002 ExecutionContext ec = context.ec
1003
1004 def entityFind = ec.entity.find("mantle.product.Product")
1005
1006 // Apply filters if provided
1007 if (productCategoryId) {
1008 entityFind.condition("productCategoryId", productCategoryId)
1009 }
1010 if (ownerPartyId) {
1011 entityFind.condition("ownerPartyId", ownerPartyId)
1012 }
1013
1014 // Get total count
1015 totalCount = entityFind.count()
1016
1017 // Apply pagination
1018 entityFind.orderBy("productName").limit(pageSize).offset(pageIndex * pageSize)
1019
1020 // Get product list with basic fields
1021 products = entityFind.selectFields(["productId", "productName", "description", "productTypeId",
1022 "productCategoryId", "ownerPartyId", "internalName"]).list()
1023
1024 ]]></script>
1025 </actions>
1026 </service>
1027 985
1028 <!-- Screen-based MCP Services --> 986 <!-- Screen-based MCP Services -->
1029 987
......
1 /*
2 * This software is in the public domain under CC0 1.0 Universal plus a
3 * Grant of Patent License.
4 *
5 * To the extent possible under law, the author(s) have dedicated all
6 * copyright and related and neighboring rights to this software to the
7 * public domain worldwide. This software is distributed without any
8 * warranty.
9 *
10 * You should have received a copy of the CC0 Public Domain Dedication
11 * along with this software (see the LICENSE.md file). If not, see
12 * <http://creativecommons.org/publicdomain/zero/1.0/>.
13 */
14 package org.moqui.mcp
15
16 import groovy.transform.CompileStatic
17 import org.moqui.context.*
18 import org.moqui.context.MessageFacade.MessageInfo
19 import org.moqui.impl.context.ExecutionContextFactoryImpl
20 import org.moqui.impl.context.ContextJavaUtil
21
22 import javax.servlet.ServletContext
23 import javax.servlet.http.HttpServletRequest
24 import javax.servlet.http.HttpServletResponse
25 import javax.servlet.http.HttpSession
26 import java.util.ArrayList
27 import java.util.EventListener
28
29 /** Stub implementation of WebFacade for testing/screen rendering without a real HTTP request */
30 @CompileStatic
31 class WebFacadeStub implements WebFacade {
32 protected final ExecutionContextFactoryImpl ecfi
33 protected final Map<String, Object> parameters
34 protected final Map<String, Object> sessionAttributes
35 protected final String requestMethod
36
37 protected HttpServletRequest httpServletRequest
38 protected HttpServletResponse httpServletResponse
39 protected HttpSession httpSession
40
41 protected Map<String, Object> requestAttributes = [:]
42 protected Map<String, Object> applicationAttributes = [:]
43 protected Map<String, Object> errorParameters = [:]
44
45 protected List<MessageInfo> savedMessages = []
46 protected List<MessageInfo> savedPublicMessages = []
47 protected List<String> savedErrors = []
48 protected List<ValidationError> savedValidationErrors = []
49
50 protected List<Map> screenHistory = []
51
52 protected String responseText = null
53 protected Object responseJsonObj = null
54 boolean skipJsonSerialize = false
55
56 WebFacadeStub(ExecutionContextFactoryImpl ecfi, Map<String, Object> parameters,
57 Map<String, Object> sessionAttributes, String requestMethod) {
58 this.ecfi = ecfi
59 this.parameters = parameters ?: [:]
60 this.sessionAttributes = sessionAttributes ?: [:]
61 this.requestMethod = requestMethod ?: "GET"
62
63 // Create mock HTTP objects
64 createMockHttpObjects()
65 }
66
67 protected void createMockHttpObjects() {
68 // Create mock HttpServletRequest
69 this.httpServletRequest = new MockHttpServletRequest(this.parameters, this.requestMethod)
70
71 // Create mock HttpServletResponse with String output capture
72 this.httpServletResponse = new MockHttpServletResponse()
73
74 // Create mock HttpSession
75 this.httpSession = new MockHttpSession(this.sessionAttributes)
76
77 // Note: Objects are linked through the mock implementations
78 }
79
80 @Override
81 String getRequestUrl() {
82 return "http://localhost:8080/test"
83 }
84
85 @Override
86 Map<String, Object> getParameters() {
87 Map<String, Object> combined = [:]
88 combined.putAll(parameters)
89 combined.putAll(getRequestParameters())
90 combined.putAll(getSessionAttributes())
91 combined.putAll(getRequestAttributes())
92 combined.putAll(getApplicationAttributes())
93 return combined
94 }
95
96 @Override
97 HttpServletRequest getRequest() { return httpServletRequest }
98
99 @Override
100 Map<String, Object> getRequestAttributes() { return requestAttributes }
101
102 @Override
103 Map<String, Object> getRequestParameters() { return parameters }
104
105 @Override
106 Map<String, Object> getSecureRequestParameters() { return parameters }
107
108 @Override
109 String getHostName(boolean withPort) {
110 return withPort ? "localhost:8080" : "localhost"
111 }
112
113 @Override
114 String getPathInfo() { return "/test" }
115
116 @Override
117 ArrayList<String> getPathInfoList() {
118 return new ArrayList<String>(["test"])
119 }
120
121 @Override
122 String getRequestBodyText() { return null }
123
124 @Override
125 String getResourceDistinctValue() { return "test" }
126
127 @Override
128 HttpServletResponse getResponse() { return httpServletResponse }
129
130 @Override
131 HttpSession getSession() { return httpSession }
132
133 @Override
134 Map<String, Object> getSessionAttributes() { return sessionAttributes }
135
136 @Override
137 String getSessionToken() { return "test-token" }
138
139 @Override
140 ServletContext getServletContext() {
141 return new MockServletContext()
142 }
143
144 @Override
145 Map<String, Object> getApplicationAttributes() { return applicationAttributes }
146
147 @Override
148 String getWebappRootUrl(boolean requireFullUrl, Boolean useEncryption) {
149 return requireFullUrl ? "http://localhost:8080" : ""
150 }
151
152 @Override
153 Map<String, Object> getErrorParameters() { return errorParameters }
154
155 @Override
156 List<MessageInfo> getSavedMessages() { return savedMessages }
157
158 @Override
159 List<MessageInfo> getSavedPublicMessages() { return savedPublicMessages }
160
161 @Override
162 List<String> getSavedErrors() { return savedErrors }
163
164 @Override
165 List<ValidationError> getSavedValidationErrors() { return savedValidationErrors }
166
167 @Override
168 List<ValidationError> getFieldValidationErrors(String fieldName) {
169 return savedValidationErrors.findAll { it.field == fieldName }
170 }
171
172 @Override
173 List<Map> getScreenHistory() { return screenHistory }
174
175 @Override
176 void sendJsonResponse(Object responseObj) {
177 if (!skipJsonSerialize) {
178 this.responseJsonObj = responseObj
179 this.responseText = ContextJavaUtil.jacksonMapper.writeValueAsString(responseObj)
180 } else {
181 this.responseJsonObj = responseObj
182 this.responseText = responseObj.toString()
183 }
184 }
185
186 @Override
187 void sendJsonError(int statusCode, String message, Throwable origThrowable) {
188 this.responseText = "Error ${statusCode}: ${message}"
189 }
190
191 @Override
192 void sendTextResponse(String text) {
193 this.responseText = text
194 }
195
196 @Override
197 void sendTextResponse(String text, String contentType, String filename) {
198 this.responseText = text
199 }
200
201 @Override
202 void sendResourceResponse(String location) {
203 this.responseText = "Resource: ${location}"
204 }
205
206 @Override
207 void sendResourceResponse(String location, boolean inline) {
208 this.responseText = "Resource: ${location} (inline: ${inline})"
209 }
210
211 @Override
212 void sendError(int errorCode, String message, Throwable origThrowable) {
213 this.responseText = "Error ${errorCode}: ${message}"
214 }
215
216 @Override
217 void handleJsonRpcServiceCall() {
218 this.responseText = "JSON-RPC not implemented in stub"
219 }
220
221 @Override
222 void handleEntityRestCall(List<String> extraPathNameList, boolean masterNameInPath) {
223 this.responseText = "Entity REST not implemented in stub"
224 }
225
226 @Override
227 void handleServiceRestCall(List<String> extraPathNameList) {
228 this.responseText = "Service REST not implemented in stub"
229 }
230
231 @Override
232 void handleSystemMessage(List<String> extraPathNameList) {
233 this.responseText = "System message not implemented in stub"
234 }
235
236 // Helper methods for ScreenTestImpl
237 String getResponseText() { return responseText }
238 Object getResponseJsonObj() { return responseJsonObj }
239
240 // Mock HTTP classes
241 static class MockHttpServletRequest implements HttpServletRequest {
242 private final Map<String, Object> parameters
243 private final String method
244 private HttpSession session
245
246 MockHttpServletRequest(Map<String, Object> parameters, String method) {
247 this.parameters = parameters ?: [:]
248 this.method = method ?: "GET"
249 }
250
251 void setSession(HttpSession session) { this.session = session }
252
253 @Override String getMethod() { return method }
254 @Override String getScheme() { return "http" }
255 @Override String getServerName() { return "localhost" }
256 @Override int getServerPort() { return 8080 }
257 @Override String getRequestURI() { return "/test" }
258 @Override String getContextPath() { return "" }
259 @Override String getServletPath() { return "" }
260 @Override String getQueryString() { return null }
261 @Override String getParameter(String name) { return parameters.get(name) as String }
262 @Override Map<String, String[]> getParameterMap() {
263 return parameters.collectEntries { k, v -> [k, [v?.toString()] as String[]] }
264 }
265 @Override String[] getParameterValues(String name) {
266 def value = parameters.get(name)
267 return value ? [value.toString()] as String[] : null
268 }
269 @Override HttpSession getSession() { return session }
270 @Override HttpSession getSession(boolean create) { return session }
271 @Override String getHeader(String name) { return null }
272 @Override java.util.Enumeration<String> getHeaderNames() { return Collections.enumeration([]) }
273 @Override java.util.Enumeration<String> getHeaders(String name) { return Collections.enumeration([]) }
274 @Override String getRemoteAddr() { return "127.0.0.1" }
275 @Override String getRemoteHost() { return "localhost" }
276 @Override boolean isSecure() { return false }
277 @Override String getCharacterEncoding() { return "UTF-8" }
278 @Override void setCharacterEncoding(String env) throws java.io.UnsupportedEncodingException {}
279 @Override int getContentLength() { return 0 }
280 @Override String getContentType() { return null }
281 @Override java.io.BufferedReader getReader() throws java.io.IOException {
282 return new BufferedReader(new StringReader(""))
283 }
284 @Override String getProtocol() { return "HTTP/1.1" }
285
286 // Other required methods with minimal implementations
287 @Override Object getAttribute(String name) { return null }
288 @Override void setAttribute(String name, Object value) {}
289 @Override void removeAttribute(String name) {}
290 @Override java.util.Enumeration<String> getAttributeNames() { return Collections.enumeration([]) }
291 @Override String getAuthType() { return null }
292 @Override String getRemoteUser() { return null }
293 @Override boolean isUserInRole(String role) { return false }
294 @Override java.security.Principal getUserPrincipal() { return null }
295 @Override String getRequestedSessionId() { return null }
296 @Override StringBuffer getRequestURL() { return new StringBuffer("http://localhost:8080/test") }
297 @Override String getPathInfo() { return "/test" }
298 @Override String getPathTranslated() { return null }
299 @Override boolean isRequestedSessionIdValid() { return false }
300 @Override boolean isRequestedSessionIdFromCookie() { return false }
301 @Override boolean isRequestedSessionIdFromURL() { return false }
302 @Override java.util.Locale getLocale() { return Locale.US }
303 @Override java.util.Enumeration<java.util.Locale> getLocales() { return Collections.enumeration([Locale.US]) }
304 @Override javax.servlet.ServletInputStream getInputStream() throws java.io.IOException {
305 return new javax.servlet.ServletInputStream() {
306 @Override boolean isReady() { return true }
307 @Override void setReadListener(javax.servlet.ReadListener readListener) {}
308 @Override int read() throws java.io.IOException { return -1 }
309 @Override boolean isFinished() { return true }
310 }
311 }
312 @Override String getLocalAddr() { return "127.0.0.1" }
313 @Override String getLocalName() { return "localhost" }
314 @Override int getLocalPort() { return 8080 }
315 @Override ServletContext getServletContext() { return null }
316 @Override boolean isAsyncStarted() { return false }
317 @Override boolean isAsyncSupported() { return false }
318 @Override javax.servlet.AsyncContext getAsyncContext() { return null }
319 @Override javax.servlet.DispatcherType getDispatcherType() { return null }
320
321 // Additional required methods for HttpServletRequest
322 @Override long getContentLengthLong() { return 0 }
323 @Override java.util.Enumeration<String> getParameterNames() { return Collections.enumeration(parameters.keySet()) }
324 @Override javax.servlet.RequestDispatcher getRequestDispatcher(String path) { return null }
325 @Override String getRealPath(String path) { return null }
326 @Override int getRemotePort() { return 0 }
327 @Override javax.servlet.AsyncContext startAsync() { return null }
328 @Override javax.servlet.AsyncContext startAsync(javax.servlet.ServletRequest request, javax.servlet.ServletResponse response) { return null }
329 @Override javax.servlet.http.Cookie[] getCookies() { return null }
330 @Override long getDateHeader(String name) { return 0 }
331 @Override int getIntHeader(String name) { return 0 }
332 @Override String changeSessionId() { return session ? session.getId() : "mock-session-id" }
333 @Override boolean isRequestedSessionIdFromUrl() { return false }
334 @Override boolean authenticate(javax.servlet.http.HttpServletResponse response) { return false }
335 @Override void login(String username, String password) {}
336 @Override void logout() {}
337 @Override java.util.Collection<javax.servlet.http.Part> getParts() { return [] }
338 @Override javax.servlet.http.Part getPart(String name) { return null }
339 @Override <T extends javax.servlet.http.HttpUpgradeHandler> T upgrade(Class<T> handlerClass) { return null }
340 }
341
342 static class MockHttpServletResponse implements HttpServletResponse {
343 private StringWriter writer = new StringWriter()
344 private PrintWriter printWriter = new PrintWriter(writer)
345 private HttpSession mockSession
346 private int status = 200
347 private String contentType = "text/html"
348 private String characterEncoding = "UTF-8"
349 private Map<String, String> headers = [:]
350
351 void setMockSession(HttpSession session) { this.mockSession = session }
352
353 @Override PrintWriter getWriter() throws java.io.IOException { return printWriter }
354 @Override javax.servlet.ServletOutputStream getOutputStream() throws java.io.IOException {
355 return new javax.servlet.ServletOutputStream() {
356 @Override boolean isReady() { return true }
357 @Override void setWriteListener(javax.servlet.WriteListener writeListener) {}
358 @Override void write(int b) throws java.io.IOException { writer.write(b) }
359 }
360 }
361
362 @Override void setStatus(int sc) { this.status = sc }
363 @Override int getStatus() { return status }
364 @Override void setContentType(String type) { this.contentType = type }
365 @Override String getContentType() { return contentType }
366 @Override void setCharacterEncoding(String charset) { this.characterEncoding = charset }
367 @Override String getCharacterEncoding() { return characterEncoding }
368 @Override void setHeader(String name, String value) { headers[name] = value }
369 @Override void addHeader(String name, String value) { headers[name] = value }
370 @Override String getHeader(String name) { return headers[name] }
371 @Override java.util.Collection<String> getHeaders(String name) {
372 return headers[name] ? [headers[name]] : []
373 }
374 @Override java.util.Collection<String> getHeaderNames() { return headers.keySet() }
375 @Override void setContentLength(int len) {}
376 @Override void setContentLengthLong(long len) {}
377 @Override void setBufferSize(int size) {}
378 @Override int getBufferSize() { return 0 }
379 @Override void flushBuffer() throws java.io.IOException { printWriter.flush() }
380 @Override void resetBuffer() {}
381 @Override boolean isCommitted() { return false }
382 @Override void reset() {}
383 @Override Locale getLocale() { return Locale.US }
384
385 String getResponseContent() { return writer.toString() }
386
387 // Other required methods with minimal implementations
388 @Override String encodeURL(String url) { return url }
389 @Override String encodeRedirectURL(String url) { return url }
390 @Override String encodeUrl(String url) { return url }
391 @Override String encodeRedirectUrl(String url) { return url }
392 @Override void sendError(int sc, String msg) throws java.io.IOException { status = sc }
393 @Override void sendError(int sc) throws java.io.IOException { status = sc }
394 @Override void sendRedirect(String location) throws java.io.IOException {}
395 @Override void setDateHeader(String name, long date) {}
396 @Override void addDateHeader(String name, long date) {}
397 @Override void setIntHeader(String name, int value) {}
398 @Override void addIntHeader(String name, int value) {}
399 @Override boolean containsHeader(String name) { return headers.containsKey(name) }
400
401 // Additional required methods for HttpServletResponse
402 @Override void setLocale(Locale locale) {}
403 @Override void addCookie(javax.servlet.http.Cookie cookie) {}
404 @Override void setStatus(int sc, String sm) { this.status = sc }
405 }
406
407 static class MockHttpSession implements HttpSession {
408 private final Map<String, Object> attributes
409 private long creationTime = System.currentTimeMillis()
410 private String id = "mock-session-" + System.currentTimeMillis()
411
412 MockHttpSession(Map<String, Object> attributes) {
413 this.attributes = attributes ?: [:]
414 }
415
416 @Override Object getAttribute(String name) { return attributes.get(name) }
417 @Override void setAttribute(String name, Object value) { attributes[name] = value }
418 @Override void removeAttribute(String name) { attributes.remove(name) }
419 @Override java.util.Enumeration<String> getAttributeNames() { return Collections.enumeration(attributes.keySet()) }
420 @Override long getCreationTime() { return creationTime }
421 @Override String getId() { return id }
422 @Override long getLastAccessedTime() { return System.currentTimeMillis() }
423 @Override javax.servlet.ServletContext getServletContext() { return null }
424 @Override void setMaxInactiveInterval(int interval) {}
425 @Override int getMaxInactiveInterval() { return 1800 }
426 @Override javax.servlet.http.HttpSessionContext getSessionContext() { return null }
427 @Override void invalidate() {}
428 @Override boolean isNew() { return false }
429 @Override void putValue(String name, Object value) { setAttribute(name, value) }
430 @Override Object getValue(String name) { return getAttribute(name) }
431 @Override void removeValue(String name) { removeAttribute(name) }
432 @Override String[] getValueNames() { return attributes.keySet() as String[] }
433 }
434
435 static class MockServletContext implements ServletContext {
436 private final Map<String, Object> attributes = [:]
437
438 @Override Object getAttribute(String name) { return attributes.get(name) }
439 @Override void setAttribute(String name, Object value) { attributes[name] = value }
440 @Override void removeAttribute(String name) { attributes.remove(name) }
441 @Override java.util.Enumeration<String> getAttributeNames() { return Collections.enumeration(attributes.keySet()) }
442 @Override String getServletContextName() { return "MockServletContext" }
443 @Override String getServerInfo() { return "Mock Server" }
444 @Override int getMajorVersion() { return 4 }
445 @Override int getMinorVersion() { return 0 }
446 @Override String getMimeType(String file) { return null }
447 @Override String getRealPath(String path) { return null }
448 @Override java.io.InputStream getResourceAsStream(String path) { return null }
449 @Override java.net.URL getResource(String path) throws java.net.MalformedURLException { return null }
450 @Override javax.servlet.RequestDispatcher getRequestDispatcher(String path) { return null }
451 @Override javax.servlet.RequestDispatcher getNamedDispatcher(String name) { return null }
452 @Override String getInitParameter(String name) { return null }
453 @Override java.util.Enumeration<String> getInitParameterNames() { return Collections.enumeration([]) }
454 @Override boolean setInitParameter(String name, String value) { return false }
455 @Override String getContextPath() { return "" }
456 @Override ServletContext getContext(String uripath) { return null }
457 @Override int getEffectiveMajorVersion() { return 4 }
458 @Override int getEffectiveMinorVersion() { return 0 }
459 @Override javax.servlet.Servlet getServlet(String name) throws javax.servlet.ServletException { return null }
460 @Override java.util.Enumeration<javax.servlet.Servlet> getServlets() { return Collections.enumeration([]) }
461 @Override java.util.Enumeration<String> getServletNames() { return Collections.enumeration([]) }
462 @Override void log(String msg) {}
463 @Override void log(Exception exception, String msg) {}
464 @Override void log(String msg, Throwable throwable) {}
465
466 // Additional required methods for ServletContext
467 @Override java.util.Set<String> getResourcePaths(String path) { return null }
468 @Override javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, String className) { return null }
469 @Override javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, javax.servlet.Servlet servlet) { return null }
470 @Override javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, Class<? extends javax.servlet.Servlet> servletClass) { return null }
471 @Override javax.servlet.ServletRegistration.Dynamic addJspFile(String jspName, String jspFile) { return null }
472 @Override <T extends javax.servlet.Servlet> T createServlet(Class<T> clazz) { return null }
473 @Override javax.servlet.ServletRegistration getServletRegistration(String servletName) { return null }
474 @Override java.util.Map<String, ? extends javax.servlet.ServletRegistration> getServletRegistrations() { return [:] }
475 @Override javax.servlet.FilterRegistration.Dynamic addFilter(String filterName, String className) { return null }
476 @Override javax.servlet.FilterRegistration.Dynamic addFilter(String filterName, javax.servlet.Filter filter) { return null }
477 @Override javax.servlet.FilterRegistration.Dynamic addFilter(String filterName, Class<? extends javax.servlet.Filter> filterClass) { return null }
478 @Override <T extends javax.servlet.Filter> T createFilter(Class<T> clazz) { return null }
479 @Override javax.servlet.FilterRegistration getFilterRegistration(String filterName) { return null }
480 @Override java.util.Map<String, ? extends javax.servlet.FilterRegistration> getFilterRegistrations() { return [:] }
481 @Override javax.servlet.SessionCookieConfig getSessionCookieConfig() { return null }
482 @Override void setSessionTrackingModes(java.util.Set<javax.servlet.SessionTrackingMode> sessionTrackingModes) {}
483 @Override java.util.Set<javax.servlet.SessionTrackingMode> getDefaultSessionTrackingModes() { return [] as Set }
484 @Override java.util.Set<javax.servlet.SessionTrackingMode> getEffectiveSessionTrackingModes() { return [] as Set }
485 @Override void addListener(String className) {}
486 @Override void addListener(EventListener listener) {}
487 @Override void addListener(Class<? extends EventListener> listenerClass) {}
488 @Override <T extends EventListener> T createListener(Class<T> clazz) { return null }
489 @Override javax.servlet.descriptor.JspConfigDescriptor getJspConfigDescriptor() { return null }
490 @Override ClassLoader getClassLoader() { return null }
491 @Override void declareRoles(String... roleNames) {}
492 @Override String getVirtualServerName() { return "localhost" }
493 @Override int getSessionTimeout() { return 30 }
494 @Override void setSessionTimeout(int sessionTimeout) {}
495 @Override String getRequestCharacterEncoding() { return "UTF-8" }
496 @Override void setRequestCharacterEncoding(String encoding) {}
497 @Override String getResponseCharacterEncoding() { return "UTF-8" }
498 @Override void setResponseCharacterEncoding(String encoding) {}
499 }
500 }
...\ No newline at end of file ...\ No newline at end of file