Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature - Add client support for enumerating a resource #158

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions client/src/jaxws/bindings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,11 @@
<jaxb:package name="io.cloudsoft.winrm4j.client.transfer" />
</jaxb:schemaBindings>
</jaxws:bindings>
<jaxws:bindings
node="wsdl:definitions/wsdl:types/xs:schema[@targetNamespace='http://schemas.xmlsoap.org/ws/2004/09/enumeration']">
<jaxb:schemaBindings>
<jaxb:package name="io.cloudsoft.winrm4j.client.enumeration" />
</jaxb:schemaBindings>
</jaxws:bindings>

</jaxws:bindings>
235 changes: 235 additions & 0 deletions client/src/main/java/io/cloudsoft/winrm4j/client/EnumerateCommand.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
package io.cloudsoft.winrm4j.client;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.ws.soap.SOAPFaultException;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.function.Predicate;
import java.util.function.Supplier;

import io.cloudsoft.winrm4j.client.enumeration.EnumerateResponse;
import io.cloudsoft.winrm4j.client.enumeration.PullResponse;
import io.cloudsoft.winrm4j.client.wsman.Enumerate;
import io.cloudsoft.winrm4j.client.wsman.Filter;
import io.cloudsoft.winrm4j.client.wsman.Items;
import io.cloudsoft.winrm4j.client.wsman.Locale;
import io.cloudsoft.winrm4j.client.wsman.OptionSetType;
import io.cloudsoft.winrm4j.client.wsman.Pull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import static io.cloudsoft.winrm4j.client.WinRmClient.MAX_ENVELOPER_SIZE;

public class EnumerateCommand implements AutoCloseable {

private static final Logger LOG = LoggerFactory.getLogger(EnumerateCommand.class.getName());

/**
* If no output is available before the wsman:OperationTimeout expires, the server MUST return a WSManFault with the Code attribute equal to "2150858793"
* https://msdn.microsoft.com/en-us/library/cc251676.aspx
*/
static final String WSMAN_FAULT_CODE_OPERATION_TIMEOUT_EXPIRED = "2150858793";

private final WinRm winrm;
private final String resourceUri;
private final String sessionId;
private final long maxElements;
private final Supplier<String> operationTimeout;
private final Supplier<Locale> locale;
private final Predicate<String> retryReceiveAfterOperationTimeout;

private final DocumentBuilder documentBuilder;

public EnumerateCommand(final WinRm winrm,
final String resourceUri,
final long maxElements,
final Supplier<String> operationTimeout,
final Supplier<Locale> locale,
final Predicate<String> retryReceiveAfterOperationTimeout) {
this.winrm = winrm;
this.resourceUri = resourceUri;
this.sessionId = "uuid:" + UUID.randomUUID();
this.maxElements = maxElements;
this.operationTimeout = operationTimeout;
this.locale = locale;
this.retryReceiveAfterOperationTimeout = retryReceiveAfterOperationTimeout;
try {
this.documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
} catch (ParserConfigurationException e) {
throw new IllegalStateException("Failed to create instance of DocumentBuilder");
}
}

public List<Node> execute(final String filter, final String dialect) {
final EnumerateResponse enumerateResponse = enumerate(filter, dialect);
final List<Node> result = new ArrayList<>();
collectAndIterateEnumeratedResults(result, new EnumerationPullState(
resourceUri,
maxElements,
enumerateResponse.getEnumerationContext(),
enumerateResponse.getItems(),
enumerateResponse.getEndOfSequence() != null
));
return result;
}

private EnumerateResponse enumerate(final String filter, final String dialect) {
while (true) {
try {
final Enumerate enumerate = new Enumerate();
enumerate.setFilter(new Filter());
enumerate.getFilter().setValue(filter);
enumerate.getFilter().setDialect(dialect);
enumerate.setMaxElements(maxElements);
return winrm.enumerate(
enumerate,
resourceUri,
sessionId,
MAX_ENVELOPER_SIZE,
operationTimeout.get(),
locale.get(),
new OptionSetType()
);
} catch (final SOAPFaultException soapFault) {
/**
* If such Exception which has a code 2150858793 the client is expected to again trigger immediately a receive request.
* https://msdn.microsoft.com/en-us/library/cc251676.aspx
*/
assertFaultCode(soapFault, WSMAN_FAULT_CODE_OPERATION_TIMEOUT_EXPIRED,
retryReceiveAfterOperationTimeout);
}
}
}

private PullResponse pull(final EnumerationPullState state) {
while (true) {
try {
final Pull pull = new Pull();
pull.setEnumerationContext(state.getEnumerationContext());
pull.setMaxElements(maxElements);
return winrm.enumeratePull(
pull,
state.getResourceId(),
sessionId,
MAX_ENVELOPER_SIZE,
operationTimeout.get(),
locale.get(),
new OptionSetType()
);
} catch (final SOAPFaultException soapFault) {
/**
* If such Exception which has a code 2150858793 the client is expected to again trigger immediately a receive request.
* https://msdn.microsoft.com/en-us/library/cc251676.aspx
*/
assertFaultCode(soapFault, WSMAN_FAULT_CODE_OPERATION_TIMEOUT_EXPIRED,
retryReceiveAfterOperationTimeout);
}
}
}

void collectAndIterateEnumeratedResults(final List<Node> result, final EnumerationPullState state) {

final Document doc = documentBuilder.newDocument();
final Element root = doc.createElement("results");
doc.appendChild(root);

final Items items = state.getItems();
if (items != null) {
final List<Object> elements = items.getAny();
if (elements != null) {
for (Object element : elements) {
if (element instanceof Node) {
final Node node = doc.importNode((Node) element, true);
root.appendChild(node);
result.add(node);
} else {
LOG.debug("{} unexpected element type {}", this, element.getClass().getCanonicalName());
}
}
}
}
// There will be additional data available if context is given and the element sequence is not ended.
if (state.getEnumerationContext() != null && !state.isEndOfSequence()) {
final PullResponse next = pull(state);
final boolean endOfSequence = next.getEndOfSequence() != null;
LOG.debug("{} endOfSequence = {}", this, endOfSequence);
collectAndIterateEnumeratedResults(result, new EnumerationPullState(
state.getResourceId(),
state.getMaxElements(),
next.getEnumerationContext(),
next.getItems(),
endOfSequence
));
}
}

void assertFaultCode(SOAPFaultException soapFault, String code, Predicate<String> retry) {
try {
NodeList faultDetails = soapFault.getFault().getDetail().getChildNodes();
for (int i = 0; i < faultDetails.getLength(); i++) {
if (faultDetails.item(i).getLocalName().equals("WSManFault")) {
if (faultDetails.item(i).getAttributes().getNamedItem("Code").getNodeValue().equals(code)
&& retry.test(code)) {
LOG.trace("winrm client {} received error 500 response with code {}, response {}", this, code, soapFault);
return;
} else {
throw soapFault;
}
}
}
throw soapFault;
} catch (NullPointerException e) {
LOG.debug("Error reading Fault Code {}", soapFault.getFault());
throw soapFault;
}
}

@Override
public void close() throws Exception {
}

static class EnumerationPullState {
private final String resourceId;
private final long maxElements;
private final String enumerationContext;
private final Items items;
private final boolean endOfSequence;

public EnumerationPullState(final String resourceId, final long maxElements, final String enumerationContext, final Items items, final boolean endOfSequence) {
this.resourceId = resourceId;
this.maxElements = maxElements;
this.enumerationContext = enumerationContext;
this.items = items;
this.endOfSequence = endOfSequence;
}

public String getResourceId() {
return resourceId;
}

public long getMaxElements() {
return maxElements;
}

public String getEnumerationContext() {
return enumerationContext;
}

public Items getItems() {
return items;
}

public boolean isEndOfSequence() {
return endOfSequence;
}
}

}
37 changes: 37 additions & 0 deletions client/src/main/java/io/cloudsoft/winrm4j/client/WinRmClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
import io.cloudsoft.winrm4j.client.wsman.OptionSetType;
import io.cloudsoft.winrm4j.client.wsman.OptionType;
import sun.awt.image.ImageWatched.Link;
import org.w3c.dom.Node;

/**
* TODO confirm if commands can be called in parallel in one shell (probably not)!
Expand Down Expand Up @@ -565,6 +566,42 @@ private static String getShellId(ResourceCreated resourceCreated) {
throw new IllegalStateException("Shell ID not fount in " + resourceCreated);
}

/**
* Executes a WMI query and returns all results as a list.
*
* @param namespace wmi namespace, default may be "root/cimv2/*"
* @param query wmi query, e.g. "Select * From Win32_TimeZone"
* @return list of nodes
*/
public List<Node> runWql(String namespace, String query) {
String resourceUri = "http://schemas.microsoft.com/wbem/wsman/1/wmi/" + namespace;
String dialect = "http://schemas.microsoft.com/wbem/wsman/1/WQL";
return enumerateAndPull(resourceUri, dialect, query);
}

/**
* Executes, enumerates and returns the result list.
*
* @param resourceUri remote resource uri to filter (must support enumeration)
* @param dialect filter dialect
* @param filter resource filter
* @return list of nodes
*/
public List<Node> enumerateAndPull(String resourceUri, String dialect, String filter) {
try (EnumerateCommand command = new EnumerateCommand(
winrm,
resourceUri,
32000L,
() -> operationTimeout,
() -> locale,
retryReceiveAfterOperationTimeout
)) {
return command.execute(filter, dialect);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}

/**
* @deprecated since 0.6.0. Use {@link ShellCommand#close()} instead.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@

import javax.xml.ws.WebServiceException;

import io.cloudsoft.winrm4j.client.enumeration.EnumerateResponse;
import io.cloudsoft.winrm4j.client.enumeration.PullResponse;
import io.cloudsoft.winrm4j.client.wsman.Enumerate;
import io.cloudsoft.winrm4j.client.wsman.Pull;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

Expand Down Expand Up @@ -197,5 +201,19 @@ public ResourceCreated create(Shell shell, String resourceURI, int maxEnvelopeSi
calls.add(call);
return (ResourceCreated) handler.apply(call);
}

@Override
public EnumerateResponse enumerate(Enumerate enumerate, String resourceURI, String sessionId, int maxEnvelopeSize, String operationTimeout, Locale locale, OptionSetType optionSet) {
RecordedCall call = new RecordedCall("enumerate", Arrays.asList(enumerate, resourceURI, sessionId, maxEnvelopeSize, operationTimeout, locale, optionSet));
calls.add(call);
return (EnumerateResponse) handler.apply(call);
}

@Override
public PullResponse enumeratePull(Pull pull, String resourceURI, String sessionId, int maxEnvelopeSize, String operationTimeout, Locale locale, OptionSetType optionSet) {
RecordedCall call = new RecordedCall("enumeratePull", Arrays.asList(pull, resourceURI, sessionId, maxEnvelopeSize, operationTimeout, locale, optionSet));
calls.add(call);
return (PullResponse) handler.apply(call);
}
}
}
51 changes: 51 additions & 0 deletions service/src/main/java/io/cloudsoft/winrm4j/service/WinRm.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
import javax.xml.ws.BindingType;
import javax.xml.ws.RequestWrapper;

import io.cloudsoft.winrm4j.service.enumerate.EnumerateRequest;
import io.cloudsoft.winrm4j.service.enumerate.EnumerateResponse;
import io.cloudsoft.winrm4j.service.enumerate.PullRequest;
import io.cloudsoft.winrm4j.service.enumerate.PullResponse;
import io.cloudsoft.winrm4j.service.shell.Receive;
import io.cloudsoft.winrm4j.service.shell.ReceiveResponse;
import io.cloudsoft.winrm4j.service.shell.Shell;
Expand Down Expand Up @@ -135,6 +139,53 @@ public ResourceCreated create(
@WebParam(name = "OptionSet", targetNamespace = "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd", header = true)
OptionSetType optionSet
) {

return null;
}

@WebMethod(operationName = "Enumerate", action = "http://schemas.xmlsoap.org/ws/2004/09/enumeration/Enumerate")
@Action(input = "http://schemas.xmlsoap.org/ws/2004/09/enumeration/Enumerate", output = "http://schemas.xmlsoap.org/ws/2004/09/enumeration/EnumerateResponse")
@WebResult(name = "EnumerateResponse", targetNamespace = "http://schemas.xmlsoap.org/ws/2004/09/enumeration", partName = "EnumerateResponse")
@SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
public EnumerateResponse enumerate(
@WebParam(name = "Enumerate", targetNamespace = "http://schemas.xmlsoap.org/ws/2004/09/enumeration")
EnumerateRequest enumerate,
@WebParam(name = "ResourceURI", targetNamespace = "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd", header = true)
String resourceURI,
@WebParam(name = "SessionId", targetNamespace = "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd", header = true)
String sessionId,
@WebParam(name = "MaxEnvelopeSize", targetNamespace = "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd", header = true)
int maxEnvelopeSize,
@WebParam(name = "OperationTimeout", targetNamespace = "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd", header = true)
String operationTimeout,
@WebParam(name = "Locale", targetNamespace = "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd", header = true)
Locale locale,
@WebParam(name = "OptionSet", targetNamespace = "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd", header = true)
OptionSetType optionSet
) {
return null;
}

@WebMethod(operationName = "EnumeratePull", action = "http://schemas.xmlsoap.org/ws/2004/09/enumeration/Pull")
@Action(input = "http://schemas.xmlsoap.org/ws/2004/09/enumeration/Pull", output = "http://schemas.xmlsoap.org/ws/2004/09/enumeration/PullResponse")
@WebResult(name = "PullResponse", targetNamespace = "http://schemas.xmlsoap.org/ws/2004/09/enumeration", partName = "PullResponse")
@SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
public PullResponse enumeratePull(
@WebParam(name = "Pull", targetNamespace = "http://schemas.xmlsoap.org/ws/2004/09/enumeration")
PullRequest pull,
@WebParam(name = "ResourceURI", targetNamespace = "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd", header = true)
String resourceURI,
@WebParam(name = "SessionId", targetNamespace = "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd", header = true)
String sessionId,
@WebParam(name = "MaxEnvelopeSize", targetNamespace = "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd", header = true)
int maxEnvelopeSize,
@WebParam(name = "OperationTimeout", targetNamespace = "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd", header = true)
String operationTimeout,
@WebParam(name = "Locale", targetNamespace = "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd", header = true)
Locale locale,
@WebParam(name = "OptionSet", targetNamespace = "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd", header = true)
OptionSetType optionSet
) {
return null;
}

Expand Down
Loading