Repository: spring-cloud/spring-cloud-dataflow
Issue: NPE when module info doesn't exist

For the command `module info type:name` and if the app doesn't exist, then NPE is thrown.

```
dataflow:>module info processor:test
Command failed java.lang.NullPointerException
java.lang.NullPointerException
    at org.springframework.cloud.dataflow.shell.command.ModuleCommands.info(ModuleCommands.java:101)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:216)
    at org.springframework.shell.core.SimpleExecutionStrategy.invoke(SimpleExecutionStrategy.java:64)
    at org.springframework.shell.core.SimpleExecutionStrategy.execute(SimpleExecutionStrategy.java:57)
    at org.springframework.shell.core.AbstractShell.executeCommand(AbstractShell.java:130)
    at org.springframework.shell.core.JLineShell.promptLoop(JLineShell.java:533)
    at org.springframework.shell.core.JLineShell.run(JLineShell.java:179)
    at java.lang.Thread.run(Thread.java:745)
```


Changed files:
spring-cloud-dataflow-shell/src/test/java/org/springframework/cloud/dataflow/shell/command/ModuleCommandsTests.java
spring-cloud-dataflow-shell/src/main/java/org/springframework/cloud/dataflow/shell/command/ModuleCommands.java

diff --git a/spring-cloud-dataflow-shell/src/main/java/org/springframework/cloud/dataflow/shell/command/ModuleCommands.java b/spring-cloud-dataflow-shell/src/main/java/org/springframework/cloud/dataflow/shell/command/ModuleCommands.java
index 3f507c9d3..868f7282d 100644
--- a/spring-cloud-dataflow-shell/src/main/java/org/springframework/cloud/dataflow/shell/command/ModuleCommands.java
+++ b/spring-cloud-dataflow-shell/src/main/java/org/springframework/cloud/dataflow/shell/command/ModuleCommands.java
@@ -98,36 +98,41 @@ public class ModuleCommands implements CommandMarker, ResourceLoaderAware {
 					help = "name of the module to query in the form of 'type:name'")
 			QualifiedModuleName module) {
 		DetailedModuleRegistrationResource info = moduleOperations().info(module.name, module.type);
-		List<ConfigurationMetadataProperty> options = info.getOptions();
 		List<Object> result = new ArrayList<>();
-		result.add(String.format("Information about %s module '%s':", module.type, module.name));
+		if (info != null) {
+			List<ConfigurationMetadataProperty> options = info.getOptions();
+			result.add(String.format("Information about %s module '%s':", module.type, module.name));
 
-		result.add(String.format("Resource URI: %s", info.getUri()));
+			result.add(String.format("Resource URI: %s", info.getUri()));
 
-		if (info.getShortDescription() != null) {
-			result.add(info.getShortDescription());
-		}
-		if (options == null) {
-			result.add("Module options metadata is not available");
-		}
-		else {
-			TableModelBuilder<Object> modelBuilder = new TableModelBuilder<>();
-			modelBuilder.addRow()
-					.addValue("Option Name")
-					.addValue("Description")
-					.addValue("Default")
-					.addValue("Type");
-			for (ConfigurationMetadataProperty option : options) {
+			if (info.getShortDescription() != null) {
+				result.add(info.getShortDescription());
+			}
+			if (options == null) {
+				result.add("Module options metadata is not available");
+			}
+			else {
+				TableModelBuilder<Object> modelBuilder = new TableModelBuilder<>();
 				modelBuilder.addRow()
-						.addValue(option.getId())
-						.addValue(option.getDescription() == null ? "<unknown>" : option.getDescription())
-						.addValue(prettyPrintDefaultValue(option))
-						.addValue(option.getType() == null ? "<unknown>" : option.getType());
+						.addValue("Option Name")
+						.addValue("Description")
+						.addValue("Default")
+						.addValue("Type");
+				for (ConfigurationMetadataProperty option : options) {
+					modelBuilder.addRow()
+							.addValue(option.getId())
+							.addValue(option.getDescription() == null ? "<unknown>" : option.getDescription())
+							.addValue(prettyPrintDefaultValue(option))
+							.addValue(option.getType() == null ? "<unknown>" : option.getType());
+				}
+				TableBuilder builder = DataFlowTables.applyStyle(new TableBuilder(modelBuilder.build()))
+						.on(CellMatchers.table()).addSizer(new AbsoluteWidthSizeConstraints(30))
+						.and();
+				result.add(builder.build());
 			}
-			TableBuilder builder = DataFlowTables.applyStyle(new TableBuilder(modelBuilder.build()))
-					.on(CellMatchers.table()).addSizer(new AbsoluteWidthSizeConstraints(30))
-					.and();
-			result.add(builder.build());
+		}
+		else {
+			result.add(String.format("Module info is not available for %s:%s", module.type, module.name));
 		}
 		return result;
 	}
diff --git a/spring-cloud-dataflow-shell/src/test/java/org/springframework/cloud/dataflow/shell/command/ModuleCommandsTests.java b/spring-cloud-dataflow-shell/src/test/java/org/springframework/cloud/dataflow/shell/command/ModuleCommandsTests.java
index 89021a4b9..8305078a7 100644
--- a/spring-cloud-dataflow-shell/src/test/java/org/springframework/cloud/dataflow/shell/command/ModuleCommandsTests.java
+++ b/spring-cloud-dataflow-shell/src/test/java/org/springframework/cloud/dataflow/shell/command/ModuleCommandsTests.java
@@ -98,7 +98,13 @@ public class ModuleCommandsTests {
 				assertThat(model.getValue(row, col), Matchers.is(expected[row][col]));
 			}
 		}
- 	}
+	}
+
+	@Test
+	public void testUnknownModule() {
+		List<Object> result = moduleCommands.info(new ModuleCommands.QualifiedModuleName("unknown", ArtifactType.processor));
+		assertEquals((String) result.get(0), "Module info is not available for processor:unknown");
+	}
 
 	@Test
 	public void register() {