File uploads in embedded forms

I am using the following code snippet for a file upload in an embedded form:
File Upload


On the server side I am printing all process variables onto the console with an ExecutionListener:
Map<String, Object> vars = de.getVariables();
for (Map.Entry<String, Object> entry : vars.entrySet()){
System.out.println(entry.getKey() + “/” + entry.getValue());
}

In the case when no file was chosen in the embedded form and the form is sent to the server by clicking “complete” a java.lang.NullPointerException is thrown.
What am I doing wrong?

Hi @thkeller,

could you please attach a full stacktrace?

Cheers,
Askar

20-Jun-2017 16:32:17.236 SEVERE [http-nio-8080-exec-532] org.apache.catalina.core.StandardWrapperValve.invoke Servlet.service() for servlet [Engine Api] in context with path [/camunda] threw exception

java.lang.RuntimeException: org.jboss.resteasy.spi.UnhandledException: java.lang.NullPointerException

            at org.camunda.bpm.webapp.impl.security.auth.AuthenticationFilter$1.execute(AuthenticationFilter.java:61)

            at org.camunda.bpm.webapp.impl.security.auth.AuthenticationFilter$1.execute(AuthenticationFilter.java:56)

            at org.camunda.bpm.webapp.impl.security.SecurityActions.runWithAuthentications(SecurityActions.java:38)

            at org.camunda.bpm.webapp.impl.security.auth.AuthenticationFilter.doFilter(AuthenticationFilter.java:56)

            at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)

            at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)

            at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219)

            at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)

            at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)

            at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142)

            at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)

            at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:617)

            at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)

            at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:518)

            at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1091)

            at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:668)

            at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1527)

            at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1484)

            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)

            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)

            at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)

            at java.lang.Thread.run(Thread.java:745)

Caused by: org.jboss.resteasy.spi.UnhandledException: java.lang.NullPointerException

            at org.jboss.resteasy.core.SynchronousDispatcher.handleApplicationException(SynchronousDispatcher.java:365)

            at org.jboss.resteasy.core.SynchronousDispatcher.handleException(SynchronousDispatcher.java:233)

            at org.jboss.resteasy.core.SynchronousDispatcher.handleInvokerException(SynchronousDispatcher.java:209)

            at org.jboss.resteasy.core.SynchronousDispatcher.getResponse(SynchronousDispatcher.java:557)

            at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:524)

            at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:126)

            at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:208)

            at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:55)

            at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:50)

            at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)

            at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291)

            at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)

            at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)

            at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)

            at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)

            at org.camunda.bpm.engine.rest.filter.CacheControlFilter.doFilter(CacheControlFilter.java:41)

            at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)

            at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)

            at org.camunda.bpm.webapp.impl.security.filter.SecurityFilter.doFilterSecure(SecurityFilter.java:67)

            at org.camunda.bpm.webapp.impl.security.filter.SecurityFilter.doFilter(SecurityFilter.java:51)

            at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)

            at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)

            at org.camunda.bpm.webapp.impl.security.auth.AuthenticationFilter$1.execute(AuthenticationFilter.java:59)

            ... 21 more

Caused by: java.lang.NullPointerException

            at org.camunda.bpm.engine.variable.impl.type.FileValueTypeImpl.createValue(FileValueTypeImpl.java:44)

            at org.camunda.bpm.engine.rest.dto.VariableValueDto.toTypedValue(VariableValueDto.java:123)

            at org.camunda.bpm.engine.rest.dto.VariableValueDto.toMap(VariableValueDto.java:146)

            at org.camunda.bpm.engine.rest.sub.repository.impl.ProcessDefinitionResourceImpl.submitForm(ProcessDefinitionResourceImpl.java:165)

            at sun.reflect.GeneratedMethodAccessor955.invoke(Unknown Source)

            at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

            at java.lang.reflect.Method.invoke(Method.java:483)

            at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:167)

            at org.jboss.resteasy.core.ResourceMethod.invokeOnTarget(ResourceMethod.java:257)

            at org.jboss.resteasy.core.ResourceMethod.invoke(ResourceMethod.java:222)

            at org.jboss.resteasy.core.ResourceLocator.invokeOnTargetObject(ResourceLocator.java:159)

            at org.jboss.resteasy.core.ResourceLocator.invoke(ResourceLocator.java:107)

            at org.jboss.resteasy.core.ResourceLocator.invokeOnTargetObject(ResourceLocator.java:154)

            at org.jboss.resteasy.core.ResourceLocator.invoke(ResourceLocator.java:92)

            at org.jboss.resteasy.core.SynchronousDispatcher.getResponse(SynchronousDispatcher.java:542)

            ... 40 more

Hi @thkeller,

well, code on this line corresponds to
Object filename = valueInfo.get(VALUE_INFO_FILE_NAME);

SInce you are not setting any value it get deserialized as null I assume, which leads to the exception. I think you could remove it using JS if it’s not set. Here is a method reference https://github.com/camunda/camunda-bpm-sdk-js/blob/7.7/lib/forms/variable-manager.js#L44

Does that help?
Askar

Ok. I added inside camForm.on(‘submit’, function(){…} following code segment:

    if (!$scope.document) {

        camForm.variableManager.destroyVariable('document');

    }

But receiving following error in Browser when clicking “Start”

Error: Cannot remove variable with name document: variable does not exist.

Cheers

Thomas

Hi

I am having the same problem. Any other ideas would appreciated…

Cheers
Arben

Hi @thkeller and @arben.thaqi

one thing which you could do to really delete the variable from the variable manager:

  1. Change the way you check if the variable exists, e.g. by explicitly checking if the field has a value inside the camForm.fields (camForm.fields[n].element.value==null) on submit of the form.
  2. If the value is null, you can then do two things:
    camForm.variableManager.destroyVariable(‘document’);
    delete camForm.fields[n];

Besides that I usually display file uploads only if they are required or expected from the user. You could display the file upload also only if a specific scope variable is set (e.g. display file upload field after the click of some upload button)

Best
Felix

Hi

I tried it out. But this doesn’t help. It still will give an internal server error. I double checked that camForm.fields does not contain the upload variable anymore.

I wonder why just two people face this error. Isn’t it very common to upload files?

Maybe there is someone else who has solved this issue?

Kind regards

Thomas

Hey everyone.

I had the same situation, you can try out my solution outlined here:

Hi to all here,

I’m also facing the same problem because when I try to submit my task form I see an internal server error which is the following (like @thkeller’s):

org.camunda.bpm.engine.rest.exception.ExceptionHandler.toResponse java.lang.NullPointerException
	at org.camunda.bpm.engine.variable.impl.type.FileValueTypeImpl.createValue(FileValueTypeImpl.java:44)
	at org.camunda.bpm.engine.rest.dto.VariableValueDto.toTypedValue(VariableValueDto.java:123)

In my case, I add products from a Form to a Table. The user must fill in all of the input fields of the Form in order to add the product to the Table. One of those fields is a file field. Although I fill in the file field, I take again and again the same error as if it isn’t filled in in the Form.

Does anyone have any idea on this please?

My code:

<!DOCTYPE html>
<html lang="en">
	<head>
  		<meta charset="UTF-8">
  		<meta name="viewport" content="width=device-width, initial-scale=1.0">
  		<meta http-equiv="X-UA-Compatible" content="ie=edge">
  		<title>Insert products and specifications</title>
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous" />		<!-- We include Bootstrap from a CDN (Content Delivery Network). -->
 	</head>
 	<body>
  		<form role="form" name="insertForm" accept-charset="utf-8">
   			<script cam-script type="text/form-script">
    			var product = $scope.product = [];                              																																				// Custom JavaScript creates a JavaScript Object and binds it to the current AngularJS $scope of the form as a variable named "product".
    			$scope.addProduct = function () {                              																																					// We make a function named "addProduct".
     				var product = {};                                 																																							// We add a new "product" to the Array.
     				product.Category = $scope.Category;
     				product.Description = $scope.Description;
     				if (!!$scope.camForm.fields[0].element[0].files[0]) {                        																																// If the file is uploaded,
      					product.Details = $scope.camForm.fields[0].element[0].files[0].name;                   																													// it returns file's "name".
     				} else {                                   																																									// If the file is not uploaded,
      					return;                                   																																								// it returns "undefined".
     				}
     				product.Price = $scope.Price;
     				$scope.product.push(product);                              																																					// We use the value of the "product" input field to add a new "product" to the Array.
     				$scope.Category = "";                                																																						// We clear the TextBox "Κατηγορία".
     				$scope.Description = "";                               																																						// We clear the TextBox "Περιγραφή".
     				$scope.Details = "";                                																																						// We clear file's "name".
     				$scope.Price = "";                                 																																							// We clear the TextBox "Τιμή (€)".
    			};
				$scope.clear = function () {																																													// We make a function named "clear".
					angular.element("input[type='file']").val(null);
				};
    			$scope.removeProduct = function (index) {                            																																			// We make a function named "removeProduct".
     				var category = $scope.product[index].Category;                          																																	// We find product's "Category" using "index" from the Array and binds it to the current AngularJS $scope of the form as a variable named "category".
     				$scope.product.splice(index, 1);                             																																				// We use an "index" to remove a "product" from the Array.
    			}
    			$scope.isAddFormValid = function () {                             																																				// We make a function named "isAddFormValid".
     				return ($scope.Category &&
          					$scope.Description &&
       						$scope.camForm.fields[0].element[0].files[0] &&
       						$scope.Price) ? true : false;                            																																			// If all of the 4 input fields of variable "product" are filled in, the "isAddFormValid" function (expression) returns "true", otherwise the function returns "false".
    			}
    			camForm.on('form-loaded', function() {                             																																				// We hook into the lifecycle of Camunda SDK JS Form.
     				camForm.variableManager.createVariable ({                           																																		// We "create" (declare) a new "process variable"
      					name:'product',                                 																																						// named 'product' and
      					type:'json',                                 																																							// provide as type information 'json' used for serialization.
      					value:product
     				});
    			});
    			camForm.on('submit', function(evt) {                             																																				// We hook into the lifecycle of Camunda SDK JS Form.
     				if (product.length<1) {                                																																						// If no "product" is added to the Array,
      					evt.submitPrevented = true;                              																																				// an event handler prevents the form from being submitted by setting the property "submitPrevented" to 'true'.
     				}
    			});																																																				 
   			</script>
   			<h2><b>Λίστα Προϊόντων</b></h2>                                																																						<!-- We set the heading of the HTML Table. -->
   			<div>																																																				
    			<table style="width:100%;">
     				<thead>                                    																																									<!-- We group the "header" content in the HTML Table. -->
      					<tr>                                   																																									<!-- The "header" content of the HTML Table is not repeated. -->
       						<th>Κατηγορία</th>
       						<th>Περιγραφή</th>
       						<th>Λεπτομέρειες</th>
       						<th style="width:75px;">Τιμή (€)</th>
       						<th></th>
      					</tr>
     				</thead>
     				<tbody ng-repeat="x in product track by $index">                         																																	<!-- The HTML Table is populated from the JSON Array "product", using a "ng-repeat" directive which is assigned to each row of the Table in order to repeat all the objects of the Array. -->
      					<tr>                                   																																									<!-- Each row of the HTML Table consists of 4 HTML fields and 1 button. -->
       						<td><input type="text" value="{{x.Category}}" /></td>
       						<td style="width:100%; padding:0px 0px 0px 0px"><input style="width:100%;" type="text" value="{{x.Description}}" /></td>
       						<td><input type="text" value="{{x.Details}}" /></td>
       						<td><input style="width:75px;" type="number" value="{{x.Price}}" /></td>
       						<td><input type="button" ng-click="removeProduct($index)" value="Remove" /></td>               																										<!-- The "ng-click" directive is assigned to the "Remove" button and calls the function named "removeProduct" with the current "$index" when this button is clicked. -->
      					</tr>
     				</tbody>
    			</table>
   			</div>
   			<hr>                                      																																											<!-- We separate the HTML content of the page. -->
   			<div>
    			<h2><b>Καταχώρησε νέο προϊόν</b></h2>                             																																				<!-- We set the heading of the HTML Form. -->
    			<div class="row">                                  																																								<!-- We set the "1st row" of the HTML Form. -->
     				<div class="col-md-6">                                																																						<!-- We use "md" for "medium" screen devices of width "equal to or greater than" 992px and "6" for adding 6 columns. -->
      					<div class="form-group">                              																																					<!-- We use "form-group" for optimum spacing. -->
       						<label class="control-label" for="category">Επίλεξε Κατηγορία:</label>
       						<div class="controls">
        						<input list="category" name="categories" ng-model="Category" />   																																<!-- When the value of the input field "Επίλεξε Κατηγορία" changes, is bound to the created variable "Category" in AngularJS by the "ng-model" directive. -->
								<datalist id="category">
									<option value="Desktop">
									<option value="Laptop">
									<option value="Tablet">
									<option value="Οθόνη Υπολογιστή">
									<option value="Οθόνη Προβολής">
									<option value="Εκτυπωτής laser">
									<option value="Φωτοτυπικό Μηχάνημα">
									<option value="Scanner">
									<option value="UPS">
									<option value="Διαδικτυακή Συσκευή Αποθήκευσης">
									<option value="Εξωτερικός Σκληρός Δίσκος">
									<option value="Προτζέκτορας">
									<option value="Βιντεοπροτζέκτορας">
								</datalist> 
       						</div>
      					</div>
     				</div>
     				<div class="col-md-6">
      					<div class="form-group">
       						<label class="control-label" for="description">Περιγραφή</label>
       						<div class="controls">
        						<input id="description" type="text" onkeypress="this.style.width = ((this.value.length + 1) * 8) + 'px';" ng-model="Description" />  															<!-- When the value of the input field "Περιγραφή" changes, is bound to the created variable "Description" in AngularJS by the "ng-model" directive. -->
       						</div>
      					</div>
                        <div class="form-group">
                        	<label class="control-label" for="details">Λεπτομέρειες</label>
                            <div class="controls">
                            	<input id="details"
                                       type="file"
                                       cam-variable-name="Details"
                                       cam-variable-type="File"
                                       cam-max-filesize="10000000" ng-model="Details" />                                                                                														<!-- When the value of the input field "Λεπτομέρειες" changes, is bound to the created variable "Details" in AngularJS by the "ng-model" directive. -->
                            </div>
                        </div>
                        <div class="form-group">
                        	<label class="control-label" for="price">Τιμή (€)</label>
                            <div class="controls">
                            	<input style="width:75px;" id="price" type="number" min="0" ng-model="Price" />                                                         														<!-- When the value of the input field "Τιμή (€)" changes, is bound to the created variable "Price" in AngularJS by the "ng-model" directive. -->
                            </div>
                        </div>
                        <div class="controls">
                        	<input type="button" ng-click="addProduct();clear()" ng-show="isAddFormValid()" value="Add" />                                                      												<!-- The "ng-show" directive shows the input element ("Add" button) only if the "isAddFormValid()" function (expression) returns "true". The "ng-click" directive is assigned to the "Add" button and calls the functions named "addProduct()" and "clear()" when this button is clicked. -->
                        </div>
     				</div>
    			</div>
   			</div>
  		</form>
 	</body>
</html>

Thank you in advance,
Steve