private void generateActionArgument(
      ActionArgument actionArgument, Document descriptor, Element actionElement) {

    Element actionArgumentElement = appendNewElement(descriptor, actionElement, ELEMENT.argument);

    appendNewElementIfNotNull(
        descriptor, actionArgumentElement, ELEMENT.name, actionArgument.getName());
    appendNewElementIfNotNull(
        descriptor,
        actionArgumentElement,
        ELEMENT.direction,
        actionArgument.getDirection().toString().toLowerCase(Locale.ENGLISH));
    if (actionArgument.isReturnValue()) {
      // TODO: UPNP VIOLATION: WMP12 will discard RenderingControl service if it contains <retval>
      // tags
      log.warning(
          "UPnP specification violation: Not producing <retval> element to be compatible with WMP12: "
              + actionArgument);
      // appendNewElement(descriptor, actionArgumentElement, ELEMENT.retval);
    }
    appendNewElementIfNotNull(
        descriptor,
        actionArgumentElement,
        ELEMENT.relatedStateVariable,
        actionArgument.getRelatedStateVariableName());
  }
  /**
   * Finds all element nodes in the list that match any argument name or argument alias, throws
   * {@link ActionException} if not all arguments were found.
   */
  protected List<Node> getMatchingNodes(NodeList nodeList, ActionArgument[] args)
      throws ActionException {

    List<String> names = new ArrayList<>();
    for (ActionArgument argument : args) {
      names.add(argument.getName());
      names.addAll(Arrays.asList(argument.getAliases()));
    }

    List<Node> matches = new ArrayList<>();
    for (int i = 0; i < nodeList.getLength(); i++) {
      Node child = nodeList.item(i);

      if (child.getNodeType() != Node.ELEMENT_NODE) continue;

      if (names.contains(getUnprefixedNodeName(child))) matches.add(child);
    }

    if (matches.size() < args.length) {
      throw new ActionException(
          ErrorCode.ARGUMENT_VALUE_INVALID,
          "Invalid number of input or output arguments in XML message, expected "
              + args.length
              + " but found "
              + matches.size());
    }
    return matches;
  }
  protected void writeActionOutputArguments(
      Document d, Element actionResponseElement, ActionInvocation actionInvocation) {

    for (ActionArgument argument : actionInvocation.getAction().getOutputArguments()) {
      log.fine("Writing action output argument: " + argument.getName());
      String value =
          actionInvocation.getOutput(argument) != null
              ? actionInvocation.getOutput(argument).toString()
              : "";
      XMLUtil.appendNewElement(d, actionResponseElement, argument.getName(), value);
    }
  }
 /**
  * Creates an instance of {@link ActionArgumentValue} and wraps an {@link InvalidValueException}
  * as an {@link ActionException} with the appropriate {@link ErrorCode}.
  */
 protected ActionArgumentValue createValue(ActionArgument arg, String value)
     throws ActionException {
   try {
     return new ActionArgumentValue(arg, value);
   } catch (InvalidValueException ex) {
     throw new ActionException(
         ErrorCode.ARGUMENT_VALUE_INVALID,
         "Wrong type or invalid value for '" + arg.getName() + "': " + ex.getMessage(),
         ex);
   }
 }
  @SuppressWarnings({"rawtypes", "unchecked"})
  private ActionArgumentValue[] getArguments(final Action<RemoteService> action) {
    final ActionArgument[] actionArguments = action.getArguments();
    final Map<String, Object> argumentValues = getArgumentValues();
    final List<ActionArgumentValue<RemoteService>> actionArgumentValues =
        new ArrayList<>(actionArguments.length);

    for (final ActionArgument<RemoteService> actionArgument : actionArguments) {
      if (actionArgument.getDirection() == Direction.IN) {
        final Object value = argumentValues.get(actionArgument.getName());
        logger.trace(
            "Action {}: add arg value for {}: {} (expected datatype: {})",
            action.getName(),
            actionArgument,
            value,
            actionArgument.getDatatype().getDisplayString());
        actionArgumentValues.add(new ActionArgumentValue<>(actionArgument, value));
      }
    }
    return actionArgumentValues.toArray(new ActionArgumentValue[actionArgumentValues.size()]);
  }
  /**
   * The UPnP spec says that action arguments must be in the order as declared by the service. This
   * method however is lenient, the action argument nodes in the XML can be in any order, as long as
   * they are all there everything is OK.
   */
  protected ActionArgumentValue[] readArgumentValues(NodeList nodeList, ActionArgument[] args)
      throws ActionException {

    List<Node> nodes = getMatchingNodes(nodeList, args);

    ActionArgumentValue[] values = new ActionArgumentValue[args.length];

    for (int i = 0; i < args.length; i++) {

      ActionArgument arg = args[i];
      Node node = findActionArgumentNode(nodes, arg);
      if (node == null) {
        throw new ActionException(
            ErrorCode.ARGUMENT_VALUE_INVALID,
            "Could not find argument '" + arg.getName() + "' node");
      }
      log.fine("Reading action argument: " + arg.getName());
      String value = XMLUtil.getTextContent(node);
      values[i] = createValue(arg, value);
    }
    return values;
  }
 /**
  * Returns the node with the same unprefixed name as the action argument name/alias or <code>null
  * </code>.
  */
 protected Node findActionArgumentNode(List<Node> nodes, ActionArgument arg) {
   for (Node node : nodes) {
     if (arg.isNameOrAlias(getUnprefixedNodeName(node))) return node;
   }
   return null;
 }