ReadInvocationHandler( final Node node, final Method method, final String annotationValue, final XBProjector projector, final boolean absentIsEmpty) { super(node, method, annotationValue, projector); wrappedInOptional = ReflectionHelper.isOptional(method.getGenericReturnType()); returnType = wrappedInOptional ? ReflectionHelper.getParameterType(method.getGenericReturnType()) : method.getReturnType(); Class<?>[] exceptionTypes = method.getExceptionTypes(); exceptionType = exceptionTypes.length > 0 ? exceptionTypes[0] : null; this.isConvertable = projector.config().getTypeConverter().isConvertable(returnType); this.isReturnAsNode = Node.class.isAssignableFrom(returnType); this.isEvaluateAsList = List.class.equals(returnType) || ReflectionHelper.isStreamClass(returnType); this.isReturnAsStream = ReflectionHelper.isStreamClass(returnType); this.isEvaluateAsArray = returnType.isArray(); if (wrappedInOptional && (isEvaluateAsArray || isEvaluateAsList)) { throw new IllegalArgumentException( "Method " + method + " must not declare an optional return type of list or array. Lists and arrays may be empty but will never be null."); } this.isEvaluateAsSubProjection = returnType.isInterface(); this.isThrowIfAbsent = exceptionType != null; // Throwing exception overrides empty default value. this.absentIsEmpty = absentIsEmpty && (!isThrowIfAbsent); }
ProjectionMethodInvocationHandler( final Node node, final Method method, final String annotationValue, final XBProjector projector) { this.method = method; this.annotationValue = annotationValue; this.projector = projector; this.node = node; final XBDocURL annotation = method.getAnnotation(XBDocURL.class); this.docAnnotationValue = annotation == null ? null : annotation.value(); this.isVoidMethod = !ReflectionHelper.hasReturnType(method); methodParameterIndexes = ReflectionHelper.getMethodParameterIndexes(method); }
/** * When reading collections, determine the collection component type. * * @param method * @return */ private static Class<?> findTargetComponentType(final Method method) { if (method.getReturnType().isArray()) { return method.getReturnType().getComponentType(); } if (!(ReflectionHelper.isStreamClass(method.getReturnType()) || List.class.equals(method.getReturnType()))) { return null; } final Type type = method.getGenericReturnType(); if (!(type instanceof ParameterizedType) || (((ParameterizedType) type).getActualTypeArguments() == null) || (((ParameterizedType) type).getActualTypeArguments().length < 1)) { throw new IllegalArgumentException( "When using List as return type for method " + method + ", please specify a generic type for the List. Otherwise I do not know which type I should fill the List with."); } assert ((ParameterizedType) type).getActualTypeArguments().length == 1 : ""; Type componentType = ((ParameterizedType) type).getActualTypeArguments()[0]; if (!(componentType instanceof Class)) { throw new IllegalArgumentException( "I don't know how to instantiate the generic type for the return type of method " + method); } return (Class<?>) componentType; }
/** * If parameter is instance of Callable or Supplier then resolve its value. * * @param args * @param args2 */ private static void unwrapArgs(final Class<?>[] types, final Object[] args) { if (args == null) { return; } try { for (int i = 0; i < args.length; ++i) { args[i] = ReflectionHelper.unwrap(types[i], args[i]); } } catch (Exception e) { throw new IllegalArgumentException(e); } }
@Override public Object invokeXpathProjection( final InvocationContext invocationContext, final Object proxy, final Object[] args) throws Throwable { final Object result = invokeReadProjection(invocationContext, proxy, args); if ((result == null) && (isThrowIfAbsent)) { XBDataNotFoundException dataNotFoundException = new XBDataNotFoundException(invocationContext.getResolvedXPath()); if (XBDataNotFoundException.class.equals(exceptionType)) { throw dataNotFoundException; } ReflectionHelper.throwThrowable(exceptionType, args, dataNotFoundException); } return result; }
@Override public Object invokeXpathProjection( final InvocationContext invocationContext, final Object proxy, final Object[] args) throws Throwable { assert ReflectionHelper.hasParameters(method); final Node node = getNodeForMethod(method, args); // final Document document = DOMHelper.getOwnerDocumentFor(node); // final XPath xPath = projector.config().createXPath(document); final XPathExpression expression = invocationContext.getxPathExpression(); final Object valueToSet = args[findIndexOfValue]; // final Class<?> typeToSet = method.getParameterTypes()[findIndexOfValue]; // final boolean isMultiValue = isMultiValue(typeToSet); NodeList nodes = (NodeList) expression.evaluate(node, XPathConstants.NODESET); final int count = nodes.getLength(); for (int i = 0; i < count; ++i) { final Node n = nodes.item(i); if (n == null) { continue; } if (Node.ATTRIBUTE_NODE == n.getNodeType()) { Element e = ((Attr) n).getOwnerElement(); if (e == null) { continue; } DOMHelper.setOrRemoveAttribute( e, n.getNodeName(), valueToSet == null ? null : valueToSet.toString()); continue; } if (valueToSet instanceof Element) { if (!(n instanceof Element)) { throw new IllegalArgumentException( "XPath for element update need to select elements only"); } DOMHelper.replaceElement((Element) n, (Element) ((Element) valueToSet).cloneNode(true)); continue; } n.setTextContent(valueToSet == null ? null : valueToSet.toString()); } return getProxyReturnValueForMethod(proxy, method, Integer.valueOf(count)); }
protected Node getNodeForMethod(final Method method, final Object[] args) throws SAXException, IOException, ParserConfigurationException { if (docAnnotationValue != null) { String uri = projector.config().getExternalizer().resolveURL(docAnnotationValue, method, args); final Map<String, String> requestParams = ((IOBuilder) projector.io()).filterRequestParamsFromParams(uri, args); uri = Preprocessor.applyParams(uri, methodParameterIndexes, args); Class<?> callerClass = null; if (IOHelper.isResourceProtocol(uri)) { callerClass = ReflectionHelper.getCallerClass(8); } return IOHelper.getDocumentFromURL( projector.config().createDocumentBuilder(), uri, requestParams, method.getDeclaringClass(), callerClass); } return node; }
ProjectionInvocationHandler( final XBProjector projector, final Node node, final Class<?> projectionInterface, final Map<Class<?>, Object> mixins, final boolean toStringRendersXML, final boolean absentIsEmpty) { final Object defaultInvokerObject = DefaultDOMAccessInvoker.create(projectionInterface, node, projector, toStringRendersXML); final Map<MethodSignature, InvocationHandler> defaultInvocationHandlers = getDefaultInvokers(defaultInvokerObject); for (Entry<Class<?>, Object> e : mixins.entrySet()) { for (Method m : e.getKey().getMethods()) { mixinHandlers.put( MethodSignature.forMethod(m), new MixinInvoker(e.getValue(), projectionInterface)); } } handlers.putAll(defaultInvocationHandlers); List<Class<?>> allSuperInterfaces = ReflectionHelper.findAllSuperInterfaces(projectionInterface); for (Class<?> i7e : allSuperInterfaces) { for (Method m : i7e.getDeclaredMethods()) { if (Modifier.isPrivate(m.getModifiers())) { // ignore private methods continue; } final MethodSignature methodSignature = MethodSignature.forMethod(m); if (ReflectionHelper.isDefaultMethod(m)) { handlers.put(methodSignature, DEFAULT_METHOD_INVOCATION_HANDLER); final XBOverride xbOverride = m.getAnnotation(XBOverride.class); if (xbOverride != null) { handlers.put( methodSignature.overridenBy(xbOverride.value()), new OverrideByDefaultMethodInvocationHandler(m)); } continue; } if (defaultInvocationHandlers.containsKey(methodSignature)) { continue; } { final XBRead readAnnotation = m.getAnnotation(XBRead.class); if (readAnnotation != null) { handlers.put( methodSignature, new ReadInvocationHandler( node, m, readAnnotation.value(), projector, absentIsEmpty)); continue; } } { final XBUpdate updateAnnotation = m.getAnnotation(XBUpdate.class); if (updateAnnotation != null) { handlers.put( methodSignature, new UpdateInvocationHandler(node, m, updateAnnotation.value(), projector)); continue; } } { final XBWrite writeAnnotation = m.getAnnotation(XBWrite.class); if (writeAnnotation != null) { handlers.put( methodSignature, new WriteInvocationHandler(node, m, writeAnnotation.value(), projector)); continue; } } { final XBDelete delAnnotation = m.getAnnotation(XBDelete.class); if (delAnnotation != null) { handlers.put( methodSignature, new DeleteInvocationHandler(node, m, delAnnotation.value(), projector)); continue; } } if (mixinHandlers.containsKey(methodSignature)) { continue; } throw new IllegalArgumentException( "I don't known how to handle method " + m + ". Did you forget to add a XB*-annotation or to register a mixin?"); } } }
@Override public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { return ReflectionHelper.invokeDefaultMethod(method, args, proxy); }
@Override public Object invokeProjection( final String resolvedXpath, final Object proxy, final Object[] args) throws Throwable { // final String pathToElement = resolvedXpath.replaceAll("\\[@", // "[attribute::").replaceAll("/?@.*", "").replaceAll("\\[attribute::", "[@"); lastInvocationContext.updateMethodArgs(args); final Document document = DOMHelper.getOwnerDocumentFor(node); assert document != null; final Object valueToSet = args[findIndexOfValue]; final boolean isMultiValue = isMultiValue(method.getParameterTypes()[findIndexOfValue]); // ROOT element update if ("/*".equals(resolvedXpath)) { // Setting a new root element. if (isMultiValue) { throw new IllegalArgumentException( "Method " + method + " was invoked as setter changing the document root element, but tries to set multiple values."); } return handeRootElementReplacement(proxy, method, document, valueToSet); } final boolean wildCardTarget = resolvedXpath.endsWith("/*"); try { if (!lastInvocationContext.isStillValid(resolvedXpath)) { final DuplexExpression duplexExpression = wildCardTarget ? new DuplexXPathParser(projector.config().getUserDefinedNamespaceMapping()) .compile(resolvedXpath.substring(0, resolvedXpath.length() - 2)) : new DuplexXPathParser(projector.config().getUserDefinedNamespaceMapping()) .compile(resolvedXpath); MethodParamVariableResolver resolver = null; if (duplexExpression.isUsingVariables()) { resolver = new MethodParamVariableResolver( method, args, duplexExpression, projector.config().getStringRenderer(), null); duplexExpression.setXPathVariableResolver(resolver); } Class<?> targetComponentType = findTargetComponentType(method); lastInvocationContext = new InvocationContext( resolvedXpath, null, null, duplexExpression, resolver, targetComponentType, projector); } final DuplexExpression duplexExpression = lastInvocationContext.getDuplexExpression(); if (duplexExpression.getExpressionType().isMustEvalAsString()) { throw new XBPathException("Unwriteable xpath selector used ", method, resolvedXpath); } // MULTIVALUE if (isMultiValue) { if (duplexExpression.getExpressionType().equals(ExpressionType.ATTRIBUTE)) { throw new IllegalArgumentException( "Method " + method + " was invoked as setter changing some attribute, but was declared to set multiple values. I can not create multiple attributes for one path."); } final Iterable<?> iterable2Set = valueToSet == null ? Collections.emptyList() : (valueToSet.getClass().isArray()) ? ReflectionHelper.array2ObjectList(valueToSet) : (Iterable<?>) valueToSet; if (wildCardTarget) { // TODO: check support of ParameterizedType e.g. Supplier final Element parentElement = (Element) duplexExpression.ensureExistence(node); DOMHelper.removeAllChildren(parentElement); int count = 0; for (Object o : iterable2Set) { if (o == null) { continue; } ++count; if (o instanceof Node) { DOMHelper.appendClone(parentElement, (Node) o); continue; } if (o instanceof DOMAccess) { DOMHelper.appendClone(parentElement, ((DOMAccess) o).getDOMBaseElement()); continue; } throw new XBPathException( "When using a wildcard target, the type to set must be a DOM Node or another projection. Otherwise I can not determine the element name.", method, resolvedXpath); } return getProxyReturnValueForMethod(proxy, method, Integer.valueOf(count)); } final Element parentElement = duplexExpression.ensureParentExistence(node); duplexExpression.deleteAllMatchingChildren(parentElement); int count = applyIterableSetOnElement(iterable2Set, parentElement, duplexExpression); return getProxyReturnValueForMethod(proxy, method, Integer.valueOf(count)); } // ATTRIBUTES if (duplexExpression.getExpressionType().equals(ExpressionType.ATTRIBUTE)) { if (wildCardTarget) { // TODO: This may never happen, right? throw new XBPathException( "Wildcards are not allowed when writing to an attribute. I need to know to which Element I should set the attribute", method, resolvedXpath); } Attr attribute = (Attr) duplexExpression.ensureExistence(node); if (valueToSet == null) { attribute.getOwnerElement().removeAttributeNode(attribute); return getProxyReturnValueForMethod(proxy, method, Integer.valueOf(1)); } DOMHelper.setStringValue(attribute, valueToSet.toString()); return getProxyReturnValueForMethod(proxy, method, Integer.valueOf(1)); } if ((valueToSet instanceof Node) || (valueToSet instanceof DOMAccess)) { if (valueToSet instanceof Attr) { if (wildCardTarget) { throw new XBPathException( "Wildcards are not allowed when writing an attribute. I need to know to which Element I should set the attribute", method, resolvedXpath); } Element parentNode = duplexExpression.ensureParentExistence(node); if (((Attr) valueToSet).getNamespaceURI() != null) { parentNode.setAttributeNodeNS((Attr) valueToSet); return getProxyReturnValueForMethod(proxy, method, Integer.valueOf(1)); } parentNode.setAttributeNode((Attr) valueToSet); return getProxyReturnValueForMethod(proxy, method, Integer.valueOf(1)); } final Element newNodeOrigin = valueToSet instanceof DOMAccess ? ((DOMAccess) valueToSet).getDOMBaseElement() : (Element) valueToSet; final Element newNode = (Element) newNodeOrigin.cloneNode(true); DOMHelper.ensureOwnership(document, newNode); if (wildCardTarget) { Element parentElement = (Element) duplexExpression.ensureExistence(node); DOMHelper.removeAllChildren(parentElement); parentElement.appendChild(newNode); return getProxyReturnValueForMethod(proxy, method, Integer.valueOf(1)); } Element previousElement = (Element) duplexExpression.ensureExistence(node); DOMHelper.replaceElement(previousElement, newNode); return getProxyReturnValueForMethod(proxy, method, Integer.valueOf(1)); } final Element elementToChange = (Element) duplexExpression.ensureExistence(node); if (valueToSet == null) { // TODO: This should depend on the parameter type? // If param type == String, no structural change might be expected. DOMHelper.removeAllChildren(elementToChange); } else { final String asString = projector .config() .getStringRenderer() .render( valueToSet.getClass(), valueToSet, duplexExpression.getExpressionFormatPattern()); elementToChange.setTextContent(asString); } return getProxyReturnValueForMethod(proxy, method, Integer.valueOf(1)); } catch (XBPathParsingException e) { throw new XBPathException(e, method, resolvedXpath); } }
private Object invokeReadProjection( final InvocationContext invocationContext, final Object proxy, final Object[] args) throws Throwable { final Node node = getNodeForMethod(method, args); final ExpressionType expressionType = invocationContext.getDuplexExpression().getExpressionType(); final XPathExpression expression = invocationContext.getxPathExpression(); if (isConvertable) { String data; if (expressionType.isMustEvalAsString()) { data = (String) expression.evaluate(node, XPathConstants.STRING); } else { Node dataNode = (Node) expression.evaluate(node, XPathConstants.NODE); data = dataNode == null ? null : dataNode.getTextContent(); } if ((data == null) && (absentIsEmpty)) { data = ""; } try { final Object result = projector .config() .getTypeConverter() .convertTo(returnType, data, invocationContext.getExpressionFormatPattern()); return wrappedInOptional ? ReflectionHelper.createOptional(result) : result; } catch (NumberFormatException e) { throw new NumberFormatException( e.getMessage() + " XPath was:" + invocationContext.getResolvedXPath()); } } if (isReturnAsNode) { // Try to evaluate as node // if evaluated type does not match return type, ClassCastException will follow final Object result = expression.evaluate(node, XPathConstants.NODE); return wrappedInOptional ? ReflectionHelper.createOptional(result) : result; } if (isEvaluateAsList) { assert !wrappedInOptional : "Projection methods returning list will never return null"; final List<?> result = DefaultXPathEvaluator.evaluateAsList(expression, node, method, invocationContext); return isReturnAsStream ? ReflectionHelper.toStream(result) : result; } if (isEvaluateAsArray) { assert !wrappedInOptional : "Projection methods returning array will never return null"; final List<?> list = DefaultXPathEvaluator.evaluateAsList(expression, node, method, invocationContext); return list.toArray( (Object[]) java.lang.reflect.Array.newInstance(returnType.getComponentType(), list.size())); } if (isEvaluateAsSubProjection) { final Node newNode = (Node) expression.evaluate(node, XPathConstants.NODE); if (newNode == null) { return wrappedInOptional ? ReflectionHelper.createOptional(null) : null; } final DOMAccess subprojection = (DOMAccess) projector.projectDOMNode(newNode, returnType); return wrappedInOptional ? ReflectionHelper.createOptional(subprojection) : subprojection; } throw new IllegalArgumentException( "Return type " + returnType + " of method " + method + " is not supported. Please change to an projection interface, a List, an Array or one of current type converters types:" + projector.config().getTypeConverter()); }