Is it worked Nashorn JS object to java.util.Map? - java-8

I have java method
void someMethod(String str, Map map) {
...
}
From JS call this method
var map = new Object()
map.key1 = "val1"
...someMethod(str, map)
Exception:
java.lang.NoSuchMethodException: None of the fixed arity signatures
[(java.lang.String, java.util.Map)] of method org.prjctor.shell.Bash.eval
match the argument types [java.lang.String, jdk.nashorn.internal.scripts.JO]
But in Nashorn docs "Mapping of Data Types Between Java and JavaScript" said "Every JavaScript object is also a java.util.Map so APIs receiving maps will receive them directly".
What am I doing wrong?

Agree with the previous answers that you cannot do this as the docs have implied.
However you could create and pass a map as follows
..
var HashMap = Java.type('java.util.HashMap');
var map = new HashMap();
map.put('1', 'val1');
...someMethod(str, map)

The doc says ""Every JavaScript object implements the java.util.Map interface". But this sample test program shows thats not the case.
public final class NashornTestMap {
public static void main(String args[]) throws Exception{
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine nashorn = factory.getEngineByName("nashorn");
nashorn.eval(""
+"load(\"nashorn:mozilla_compat.js\");"
+ "importClass(Packages.NashornTestMap);"
+ "var map={};"
+ "map[\"Key\"]=String(\"Val\"); "
+ "var test = new NashornTestMap();"
+ "test.test(map);"
+ "");
}
public void test(Map<String, String> obj){
System.out.println(obj);
}
}
The above code give exception "Exception in thread "main" java.lang.ClassCastException: Cannot cast jdk.nashorn.internal.scripts.JO4 to java.util.Map". This link confirms this.
However you can use Map inside your script and call the java objects directly, like this.
public final class NashornTestMap {
public static void main(String args[]) throws Exception{
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine nashorn = factory.getEngineByName("nashorn");
nashorn.eval(""
+"load(\"nashorn:mozilla_compat.js\");"
+ "importClass(Packages.NashornTestMap);"
+ "var HashMap = Java.type(\"java.util.HashMap\");"
+ "var map = new HashMap();"
+ "map.put(0, \"value1\");"
+ "var test = new NashornTestMap();"
+ "test.test(map);"
+ "");
}
public void test(Map<String, String> obj){
System.out.println(obj);
}
}
Returns "{0=value1}"

Related

Mockito, how to mock call by reference method on same class

Why I can not mock callRefMethod method (call method by reference) on below code? The problem is real method of callRefMethod always being called.
public class ManageUserService {
public void callRefMethod(List<String> lsStr, boolean flag){
if (flag){
lsStr.add("one");
lsStr.add("two");
}
}
public void methodA(){
List<String> lsStr = new ArrayList<>();
lsStr.add("zero");
this.callRefMethod(lsStr, true);
for(String str : lsStr){
System.out.println(str);
}
}
}
Unit tests:
public class ManageUserServiceTest {
#InjectMocks
private ManageUserService manageUserService;
private AutoCloseable closeable;
#BeforeEach
public void init() {
closeable = MockitoAnnotations.openMocks(this);
}
#AfterEach
void closeService() throws Exception {
closeable.close();
}
#Test
void methodATest(){
List<String> lsData = new ArrayList<>();
lsData.add("start");
ManageUserService manageUserServiceA = new ManageUserService();
ManageUserService userSpy = spy(manageUserServiceA);
doNothing().when(userSpy).callRefMethod(lsData, true);
userSpy.methodA();
verify(userSpy).callRefMethod(ArgumentMatchers.any(ArrayList.class), ArgumentMatchers.any(Boolean.class));
}
}
The result :
zero
one
two
The problem is the difference between the list you're creating in the test method, which is used to match the expected parameters when "doing nothing":
List<String> lsData = new ArrayList<>();
lsData.add("start");
...
doNothing().when(userSpy).callRefMethod(lsData, true);
and the list created in the tested method, passed to the spy object:
List<String> lsStr = new ArrayList<>();
lsStr.add("zero");
this.callRefMethod(lsStr, true);
You're telling Mockito to doNothing if the list is: ["start"], but such list is never passed to the callRefMethod. ["zero"] is passed there, which does not match the expected params, so actual method is called.
Mockito uses equals to compare the actual argument with an expected parameter value - see: the documentation. To work around that ArgumentMatchers can be used.
You can either fix the value added to the list in the test or match the expected parameter in a less strict way (e.g. using anyList() matcher).
ok i did it by using : where manageUserServiceOne is spy of ManageUserService class
void methodATest(){
List<String> lsData = new ArrayList<>();
lsData.add("start");
doAnswer((invocation) -> {
System.out.println(invocation.getArgument(0).toString());
List<String> lsModify = invocation.getArgument(0);
lsModify.add("mockA");
lsModify.add("mockB");
return null;
}).when(manageUserServiceOne).callRefMethod(anyList(), anyBoolean());
manageUserServiceOne.methodA();
verify(manageUserServiceOne).callRefMethod(ArgumentMatchers.any(ArrayList.class), ArgumentMatchers.any(Boolean.class));
}

Spring Expression Directly access map keys in Selection

Consider that I have parsed a JSON String to a Map<String, Object>, which is arbitrarily nested
My JSON looks like:
{
"root": [
{"k":"v1"},
{"k":"v2"}
]
}
I tried the expression root.?[k == 'v1'], however received the following error:
EL1008E: Property or field 'k' cannot be found on object of type 'java.util.LinkedHashMap' - maybe not public?
The evaluation context needs a MapAccessor:
public static void main(String[] args) throws Exception {
String json = "{\n" +
" \"root\": [\n" +
" {\"k\":\"v1\"},\n" +
" {\"k\":\"v2\"}\n" +
" ]\n" +
"}";
ObjectMapper mapper = new ObjectMapper();
Object root = mapper.readValue(json, Object.class);
Expression expression = new SpelExpressionParser().parseExpression("root.?[k == 'v1']");
StandardEvaluationContext ctx = new StandardEvaluationContext();
ctx.addPropertyAccessor(new MapAccessor());
System.out.println(expression.getValue(ctx, root));
}
result:
[{k=v1}]
Without a MapAccessor, you need
"['root'].?[['k'] == 'v1']"
A MapAccessor will only work with map keys that don't contain periods.

Is there a way to make a custom implementation of Nashorn JSObject work with Object.keys()?

I recently asked this question How can I pass a proper method reference in so Nashorn can execute it? and got an answer that helped me get much further along with my project, but I discovered a limitation around providing a custom JSObject implementation that I don't know how to resolve.
Given this simple working JSObject that can handle most of the methods JS would invoke on it such as map:
import javax.script.*;
import jdk.nashorn.api.scripting.*;
import java.util.*;
import java.util.function.*;
public class scratch_6 {
public static void main(String[] args) throws Exception {
ScriptEngineManager m = new ScriptEngineManager();
ScriptEngine e = m.getEngineByName("nashorn");
// The following JSObject wraps this list
List<Object> l = new ArrayList<>();
l.add("hello");
l.add("world");
l.add(true);
l.add(1);
JSObject jsObj = new AbstractJSObject() {
#Override
public Object getMember(String name) {
if (name.equals("map")) {
// return a functional interface object - nashorn will treat it like
// script function!
final Function<JSObject, Object> jsObjectObjectFunction = callback -> {
List<Object> res = new ArrayList<>();
for (Object obj : l) {
// call callback on each object and add the result to new list
res.add(callback.call(null, obj));
}
// return fresh list as result of map (or this could be another wrapper)
return res;
};
return jsObjectObjectFunction;
} else {
// unknown property
return null;
}
}
};
e.put("obj", jsObj);
// map each String to it's uppercase and print result of map
e.eval("print(obj.map(function(x) '\"'+x.toString()+'\"'))");
//PROBLEM
//e.eval("print(Object.keys(obj))");
}
}
If you uncomment the last line where Object.keys(obj) is called, it will fail with the error ... is not an Object.
This appears to be because Object.keys() [ NativeObject.java:376 ] only checks whether the object is an instance of ScriptObject or of ScriptObjectMirror. If it is neither of those things, it throws the notAnObject error. :(
Ideally, user implemented JSObject objects should be exactly equivalent to script objects. But, user implemented JSObjects are almost script objects - but not quite. This is documented here -> https://wiki.openjdk.java.net/display/Nashorn/Nashorn+jsr223+engine+notes
Object.keys is one such case where it breaks. However, if you just want for..in javascript iteration support for your objects, you can implement JSObject.keySet in your class.
Example code:
import javax.script.*;
import jdk.nashorn.api.scripting.*;
import java.util.*;
public class Main {
public static void main(String[] args) throws Exception {
ScriptEngineManager m = new ScriptEngineManager();
ScriptEngine e = m.getEngineByName("nashorn");
// This JSObject wraps the following Properties object
Properties props = System.getProperties();
JSObject jsObj = new AbstractJSObject() {
#Override
public Set<String> keySet() {
return props.stringPropertyNames();
}
#Override
public Object getMember(String name) {
return props.getProperty(name);
}
};
e.put("obj", jsObj);
e.eval("for (i in obj) print(i, ' = ', obj[i])");
}
}

Get a specific service implementation based on a parameter

In my Sling app I have data presenting documents, with pages, and content nodes. We mostly server those documents as HTML, but now I would like to have a servlet to serve these documents as PDF and PPT.
Basically, I thought about implementing the factory pattern : in my servlet, dependending on the extension of the request (pdf or ppt), I would get from a DocumentBuilderFactory, the proper DocumentBuilder implementation, either PdfDocumentBuilder or PptDocumentBuilder.
So first I had this:
public class PlanExportBuilderFactory {
public PlanExportBuilder getBuilder(String type) {
PlanExportBuilder builder = null;
switch (type) {
case "pdf":
builder = new PdfPlanExportBuilder();
break;
default:
logger.error("Unsupported plan export builder, type: " + type);
}
return builder;
}
}
In the servlet:
#Component(metatype = false)
#Service(Servlet.class)
#Properties({
#Property(name = "sling.servlet.resourceTypes", value = "myApp/document"),
#Property(name = "sling.servlet.extensions", value = { "ppt", "pdf" }),
#Property(name = "sling.servlet.methods", value = "GET")
})
public class PlanExportServlet extends SlingSafeMethodsServlet {
#Reference
PlanExportBuilderFactory builderFactory;
#Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException {
Resource resource = request.getResource();
PlanExportBuilder builder = builderFactory.getBuilder(request.getRequestPathInfo().getExtension());
}
}
But the problem is that in the builder I would like to reference other services to access Sling resources, and with this solution, they're not bound.
I looked at Services Factory with OSGi but from what I've understood, you use them to configure differently the same implementation of a service.
Then I found that you can get a specific implementation by naming it, or use a property and a filter.
So I've ended up with this:
public class PlanExportBuilderFactory {
#Reference(target = "(builderType=pdf)")
PlanExportBuilder pdfPlanExportBuilder;
public PlanExportBuilder getBuilder(String type) {
PlanExportBuilder builder = null;
switch (type) {
case "pdf":
return pdfPlanExportBuilder;
default:
logger.error("Unsupported plan export builder, type: " + type);
}
return builder;
}
}
The builder defining a "builderType" property :
// AbstractPlanExportBuilder implements PlanExportBuilder interface
#Component
#Service(value=PlanExportBuilder.class)
public class PdfPlanExportBuilder extends AbstractPlanExportBuilder {
#Property(name="builderType", value="pdf")
public PdfPlanExportBuilder() {
planDocument = new PdfPlanDocument();
}
}
I would like to know if it's a good way to retrieve my PDF builder implementation regarding OSGi good practices.
EDIT 1
From Peter's answer I've tried to add multiple references but with Felix it doesn't seem to work:
#Reference(name = "planExportBuilder", cardinality = ReferenceCardinality.MANDATORY_MULTIPLE, policy = ReferencePolicy.DYNAMIC)
private Map<String, PlanExportBuilder> builders = new ConcurrentHashMap<String, PlanExportBuilder>();
protected final void bindPlanExportBuilder(PlanExportBuilder b, Map<String, Object> props) {
final String type = PropertiesUtil.toString(props.get("type"), null);
if (type != null) {
this.builders.put((String) props.get("type"), b);
}
}
protected final void unbindPlanExportBuilder(final PlanExportBuilder b, Map<String, Object> props) {
final String type = PropertiesUtil.toString(props.get("type"), null);
if (type != null) {
this.builders.remove(type);
}
}
I get these errors :
#Reference(builders) : Missing method bind for reference planExportBuilder
#Reference(builders) : Something went wrong: false - true - MANDATORY_MULTIPLE
#Reference(builders) : Missing method unbind for reference planExportBuilder
The Felix documentation here http://felix.apache.org/documentation/subprojects/apache-felix-maven-scr-plugin/scr-annotations.html#reference says for the bind method:
The default value is the name created by appending the reference name to the string bind. The method must be declared public or protected and take single argument which is declared with the service interface type
So according to this, I understand it cannot work with Felix, as I'm trying to pass two arguments. However, I found an example here that seems to match what you've suggested but I cannot make it work: https://github.com/Adobe-Consulting-Services/acs-aem-samples/blob/master/bundle/src/main/java/com/adobe/acs/samples/services/impl/SampleMultiReferenceServiceImpl.java
EDIT 2
Just had to move the reference above the class to make it work:
#References({
#Reference(
name = "planExportBuilder",
referenceInterface = PlanExportBuilder.class,
policy = ReferencePolicy.DYNAMIC,
cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE)
})
public class PlanExportServlet extends SlingSafeMethodsServlet {
Factories are evil :-) Main reason is of course the yucky class loading hacks that are usually used but also because they tend to have global knowledge. In general, you want to be able to add a bundle with a new DocumentBuilder and then that type should become available.
A more OSGi oriented solution is therefore to use service properties. This could look like:
#Component( property=HTTP_WHITEBOARD_FILTER_REGEX+"=/as")
public class DocumentServlet {
final Map<String,DocBuilder> builders = new ConcurrentHashMap<>();
public void doGet( HttpServletRequest rq, HttpServletResponse rsp )
throws IOException, ServletException {
InputStream in = getInputStream( rq.getPathInfo() );
if ( in == null )
....
String type = toType( rq.getPathInfo(), rq.getParameter("type") );
DocBuilder docbuilder = builders.get( type );
if ( docbuilder == null)
....
docbuilder.convert( type, in, rsp.getOutputStream() );
}
#Reference( cardinality=MULTIPLE, policy=DYNAMIC )
void addDocBuilder( DocBuilder db, Map<String,Object> props ) {
docbuilders.put(props.get("type"), db );
}
void removeDocBuilder(Map<String,Object> props ) {
docbuilders.remove(props.get("type"));
}
}
A DocBuilder could look like:
#Component( property = "type=ppt-pdf" )
public class PowerPointToPdf implements DocBuilder {
...
}

spring mvc processing xml with relative path to dtd

My webservice receives an xml from a third-party source, which contains a !DOCTYPE declaration. Therefore I must use the second method in my controller to parse the xml document, the first one gives me this exception:
Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException: Could not unmarshal to [class com.example.MeterBusXml]: null; nested exception is javax.xml.bind.UnmarshalException
- with linked exception:
[org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 48; DOCTYPE is disallowed when the feature "http://apache.org/xml/features/disallow-doctype-decl" set to true.]
I have no control over the application which posts the xml, so I must adapt my webservice to parse it with the dtd.
My question is, what is the spring framework's way of injecting the EntityResolver into every XMLReader instance?
#RestController
public class MeterBusDataController {
#RequestMapping (
consumes = APPLICATION_XML_VALUE,
method = POST,
path = "/meterbus1"
)
public void method1(#RequestBody MeterBusXml xml) {
System.out.println(xml);
}
#RequestMapping(
method = POST,
path = "/meterbus2"
)
public void method2(HttpServletRequest rq) throws IOException, ParserConfigurationException, SAXException, JAXBException {
JAXBContext jc = newInstance(MeterBusXml.class);
Unmarshaller um = jc.createUnmarshaller();
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setNamespaceAware(true);
spf.setValidating(true);
SAXParser sp = spf.newSAXParser();
XMLReader xr = sp.getXMLReader();
xr.setEntityResolver(new EntityResolver() {
#Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
return new InputSource(new StringReader(""));
}
});
BufferedReader reader = rq.getReader();
InputSource inputSource = new InputSource(reader);
SAXSource saxSource = new SAXSource(xr, inputSource);
MeterBusXml xml = (MeterBusXml)um.unmarshal(saxSource);
System.out.println(xml);
}
}
See the following document for an example of the mbus.xml I'm trying to unmarshal.
http://prevodniky.sk/products/product_EthMBus_common/download/Ethernet_converters_exports_v1_02_EN.pdf
I've found the root of the problem. First I tried to create and configure a Jaxb2Marshaller bean, but that did not work out. Then I realized, I need a HttpMessageConverter, so I had to override the extendMessageConverters method in the WebMvcConfigurerAdapter class, and set the required properties on Jaxb2RootElementHttpMessageConverter. This message converter does not use a Jaxb2Marshaller, but it's internal workings are very similar.
setSupportDtd(true) is required, to force the parser to accept the !DOCTYPE declaration.
setProcessExternalEntities(false) is required, because if this property is false, then the converter uses a blank EntityResolver, just as I did in method2.
#Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter {
#Override
public void extendMessageConverters(List<HttpMessageConverter<Jaxb2RootElementHttpMessageConverter?>> converters) {
for (final Iterator<HttpMessageConverter<?>> iterator = converters.iterator(); iterator.hasNext();) {
HttpMessageConverter<?> next = iterator.next();
if (next instanceof Jaxb2RootElementHttpMessageConverter) {
Jaxb2RootElementHttpMessageConverter jaxbConverter = (Jaxb2RootElementHttpMessageConverter) next;
jaxbConverter.setProcessExternalEntities(false);
jaxbConverter.setSupportDtd(true);
}
}
}
}

Resources