svn changeset:15971/svn branch:6.5tags/6.7.0.beta1
@@ -7,6 +7,8 @@ package com.vaadin.terminal; | |||
import java.io.Serializable; | |||
import java.util.Map; | |||
import com.vaadin.terminal.StreamVariable.StreamingStartEvent; | |||
/** | |||
* This interface defines the methods for painting XML to the UIDL stream. | |||
* | |||
@@ -156,9 +158,17 @@ public interface PaintTarget extends Serializable { | |||
* terminals Receivers are typically rendered for the client side as URLs, | |||
* where the client side implementation can do an http post request. | |||
* <p> | |||
* Note that a StreamVariable can only be used once per "paint". The same StreamVariable | |||
* can be used several times, but it must be repainted before the next | |||
* stream can be received. | |||
* Note that in current terminal implementation StreamVariables are cleaned | |||
* from the terminal only when: | |||
* <ul> | |||
* <li>a StreamVariable with same name replaces an old one | |||
* <li>the variable owner is no more attached | |||
* <li>the developer signals this by calling | |||
* {@link StreamingStartEvent#disposeStreamVariable()} | |||
* </ul> | |||
* Most commonly a component developer can just ignore this issue, but with | |||
* strict memory requirements and lots of StreamVariables implementations | |||
* that reserve a lot of memory this may be a critical issue. | |||
* | |||
* @param owner | |||
* the ReceiverOwner that can track the progress of streaming to | |||
@@ -171,8 +181,8 @@ public interface PaintTarget extends Serializable { | |||
* @throws PaintException | |||
* if the paint operation failed. | |||
*/ | |||
public void addVariable(VariableOwner owner, String name, StreamVariable value) | |||
throws PaintException; | |||
public void addVariable(VariableOwner owner, String name, | |||
StreamVariable value) throws PaintException; | |||
/** | |||
* Adds a long attribute to component. Atributes must be added before any |
@@ -45,8 +45,8 @@ public interface StreamVariable extends Serializable { | |||
* {@link #onProgress(long, long)} is called in a synchronized block when | |||
* the content is being received. This is potentially bit slow, so we are | |||
* calling that method only if requested. The value is requested after the | |||
* {@link #uploadStarted(StreamingStartEvent)} event, but not after | |||
* reading each buffer. | |||
* {@link #uploadStarted(StreamingStartEvent)} event, but not after reading | |||
* each buffer. | |||
* | |||
* @return true if this {@link StreamVariable} wants to by notified during | |||
* the upload of the progress of streaming. | |||
@@ -108,15 +108,21 @@ public interface StreamVariable extends Serializable { | |||
} | |||
/** | |||
* Event passed to {@link #uploadStarted(StreamingStartEvent)} method | |||
* before the streaming of the content to {@link StreamVariable} starts. | |||
* Event passed to {@link #uploadStarted(StreamingStartEvent)} method before | |||
* the streaming of the content to {@link StreamVariable} starts. | |||
*/ | |||
public interface StreamingStartEvent extends StreamingEvent { | |||
/** | |||
* The owner of the StreamVariable can call this method to inform the | |||
* terminal implementation that this StreamVariable will not be used to | |||
* accept more post. | |||
*/ | |||
public void disposeStreamVariable(); | |||
} | |||
/** | |||
* Event passed to {@link #onProgress(StreamingProgressEvent)} method | |||
* during the streaming progresses. | |||
* Event passed to {@link #onProgress(StreamingProgressEvent)} method during | |||
* the streaming progresses. | |||
*/ | |||
public interface StreamingProgressEvent extends StreamingEvent { | |||
} |
@@ -54,7 +54,6 @@ import com.vaadin.terminal.Paintable.RepaintRequestEvent; | |||
import com.vaadin.terminal.StreamVariable; | |||
import com.vaadin.terminal.StreamVariable.StreamingEndEvent; | |||
import com.vaadin.terminal.StreamVariable.StreamingErrorEvent; | |||
import com.vaadin.terminal.StreamVariable.StreamingStartEvent; | |||
import com.vaadin.terminal.Terminal.ErrorEvent; | |||
import com.vaadin.terminal.Terminal.ErrorListener; | |||
import com.vaadin.terminal.URIHandler; | |||
@@ -379,7 +378,8 @@ public abstract class AbstractCommunicationManager implements | |||
*/ | |||
protected void doHandleSimpleMultipartFileUpload(Request request, | |||
Response response, StreamVariable streamVariable, | |||
VariableOwner owner, String boundary) throws IOException { | |||
String variableName, VariableOwner owner, String boundary) | |||
throws IOException { | |||
boundary = CRLF + "--" + boundary + "--"; | |||
// multipart parsing, supports only one file for request, but that is | |||
@@ -528,8 +528,11 @@ public abstract class AbstractCommunicationManager implements | |||
throw new UploadException( | |||
"Warning: file upload ignored because the componente was read-only"); | |||
} | |||
streamToReceiver(simpleMultiPartReader, streamVariable, filename, | |||
mimeType, contentLength); | |||
boolean forgetVariable = streamToReceiver(simpleMultiPartReader, | |||
streamVariable, filename, mimeType, contentLength); | |||
if (forgetVariable) { | |||
cleanStreamVariable(owner, variableName); | |||
} | |||
} catch (Exception e) { | |||
synchronized (application) { | |||
handleChangeVariablesError(application, (Component) owner, e, | |||
@@ -551,8 +554,8 @@ public abstract class AbstractCommunicationManager implements | |||
* @throws IOException | |||
*/ | |||
protected void doHandleXhrFilePost(Request request, Response response, | |||
StreamVariable streamVariable, VariableOwner owner, | |||
int contentLength) throws IOException { | |||
StreamVariable streamVariable, String variableName, | |||
VariableOwner owner, int contentLength) throws IOException { | |||
// These are unknown in filexhr ATM, maybe add to Accept header that | |||
// is accessible in portlets | |||
@@ -569,8 +572,11 @@ public abstract class AbstractCommunicationManager implements | |||
throw new UploadException( | |||
"Warning: file upload ignored because the component was read-only"); | |||
} | |||
streamToReceiver(stream, streamVariable, filename, mimeType, | |||
contentLength); | |||
boolean forgetVariable = streamToReceiver(stream, streamVariable, | |||
filename, mimeType, contentLength); | |||
if (forgetVariable) { | |||
cleanStreamVariable(owner, variableName); | |||
} | |||
} catch (Exception e) { | |||
synchronized (application) { | |||
handleChangeVariablesError(application, (Component) owner, e, | |||
@@ -580,7 +586,17 @@ public abstract class AbstractCommunicationManager implements | |||
sendUploadResponse(request, response); | |||
} | |||
protected final void streamToReceiver(final InputStream in, | |||
/** | |||
* @param in | |||
* @param streamVariable | |||
* @param filename | |||
* @param type | |||
* @param contentLength | |||
* @return true if the streamvariable has informed that the terminal can | |||
* forget this variable | |||
* @throws UploadException | |||
*/ | |||
protected final boolean streamToReceiver(final InputStream in, | |||
StreamVariable streamVariable, String filename, String type, | |||
int contentLength) throws UploadException { | |||
if (streamVariable == null) { | |||
@@ -592,11 +608,11 @@ public abstract class AbstractCommunicationManager implements | |||
OutputStream out = null; | |||
int totalBytes = 0; | |||
StreamingStartEventImpl startedEvent = new StreamingStartEventImpl( | |||
filename, type, contentLength); | |||
try { | |||
boolean listenProgress; | |||
synchronized (application) { | |||
StreamingStartEvent startedEvent = new StreamingStartEventImpl( | |||
filename, type, contentLength); | |||
streamVariable.streamingStarted(startedEvent); | |||
out = streamVariable.getOutputStream(); | |||
listenProgress = streamVariable.listenProgress(); | |||
@@ -662,6 +678,7 @@ public abstract class AbstractCommunicationManager implements | |||
throw new UploadException(e); | |||
} | |||
} | |||
return startedEvent.isDisposed(); | |||
} | |||
private void tryToCloseStream(OutputStream out) { | |||
@@ -2181,6 +2198,9 @@ public abstract class AbstractCommunicationManager implements | |||
} | |||
abstract String createStreamVariableTargetUrl(VariableOwner owner, String name, | |||
StreamVariable value); | |||
abstract String getStreamVariableTargetUrl(VariableOwner owner, | |||
String name, StreamVariable value); | |||
abstract protected void cleanStreamVariable(VariableOwner owner, String name); | |||
} |
@@ -208,8 +208,7 @@ public class CommunicationManager extends AbstractCommunicationManager { | |||
/** | |||
* Handles file upload request submitted via Upload component. | |||
* | |||
* @see #createStreamVariableTargetUrl(ReceiverOwner, String, | |||
* StreamVariable) | |||
* @see #getStreamVariableTargetUrl(ReceiverOwner, String, StreamVariable) | |||
* | |||
* @param request | |||
* @param response | |||
@@ -231,27 +230,30 @@ public class CommunicationManager extends AbstractCommunicationManager { | |||
+ AbstractApplicationServlet.UPLOAD_URL_PREFIX.length(); | |||
String uppUri = pathInfo.substring(startOfData); | |||
String[] parts = uppUri.split("/", 3); // 0 = pid, 1= name, 2 = sec key | |||
String variableName = parts[1]; | |||
String paintableId = parts[0]; | |||
StreamVariable streamVariable = pidToNameToStreamVariable.get(parts[0]) | |||
.remove(parts[1]); | |||
String secKey = streamVariableToSeckey.remove(streamVariable); | |||
StreamVariable streamVariable = pidToNameToStreamVariable.get( | |||
paintableId).get(variableName); | |||
String secKey = streamVariableToSeckey.get(streamVariable); | |||
if (secKey.equals(parts[2])) { | |||
VariableOwner source = getVariableOwner(parts[0]); | |||
VariableOwner source = getVariableOwner(paintableId); | |||
String contentType = request.getContentType(); | |||
if (request.getContentType().contains("boundary")) { | |||
// Multipart requests contain boundary string | |||
doHandleSimpleMultipartFileUpload( | |||
new HttpServletRequestWrapper(request), | |||
new HttpServletResponseWrapper(response), | |||
streamVariable, source, | |||
streamVariable, variableName, source, | |||
contentType.split("boundary=")[1]); | |||
} else { | |||
// if boundary string does not exist, the posted file is from | |||
// XHR2.post(File) | |||
doHandleXhrFilePost(new HttpServletRequestWrapper(request), | |||
new HttpServletResponseWrapper(response), | |||
streamVariable, source, request.getContentLength()); | |||
streamVariable, variableName, source, | |||
request.getContentLength()); | |||
} | |||
} else { | |||
throw new InvalidUIDLSecurityKeyException( | |||
@@ -357,9 +359,8 @@ public class CommunicationManager extends AbstractCommunicationManager { | |||
private Map<StreamVariable, String> streamVariableToSeckey; | |||
@Override | |||
String createStreamVariableTargetUrl(VariableOwner owner, String name, | |||
String getStreamVariableTargetUrl(VariableOwner owner, String name, | |||
StreamVariable value) { | |||
/* | |||
* We will use the same APP/* URI space as ApplicationResources but | |||
* prefix url with UPLOAD | |||
@@ -389,8 +390,11 @@ public class CommunicationManager extends AbstractCommunicationManager { | |||
if (streamVariableToSeckey == null) { | |||
streamVariableToSeckey = new HashMap<StreamVariable, String>(); | |||
} | |||
String seckey = UUID.randomUUID().toString(); | |||
streamVariableToSeckey.put(value, seckey); | |||
String seckey = streamVariableToSeckey.get(value); | |||
if (seckey == null) { | |||
seckey = UUID.randomUUID().toString(); | |||
streamVariableToSeckey.put(value, seckey); | |||
} | |||
return getApplication().getURL() | |||
+ AbstractApplicationServlet.UPLOAD_URL_PREFIX + key + "/" | |||
@@ -398,4 +402,14 @@ public class CommunicationManager extends AbstractCommunicationManager { | |||
} | |||
@Override | |||
protected void cleanStreamVariable(VariableOwner owner, String name) { | |||
Map<String, StreamVariable> nameToStreamVar = pidToNameToStreamVariable | |||
.get(getPaintableId((Paintable) owner)); | |||
nameToStreamVar.remove("name"); | |||
if (nameToStreamVar.isEmpty()) { | |||
pidToNameToStreamVariable.remove(getPaintableId((Paintable) owner)); | |||
} | |||
} | |||
} |
@@ -29,8 +29,8 @@ import com.vaadin.terminal.ExternalResource; | |||
import com.vaadin.terminal.PaintException; | |||
import com.vaadin.terminal.PaintTarget; | |||
import com.vaadin.terminal.Paintable; | |||
import com.vaadin.terminal.StreamVariable; | |||
import com.vaadin.terminal.Resource; | |||
import com.vaadin.terminal.StreamVariable; | |||
import com.vaadin.terminal.ThemeResource; | |||
import com.vaadin.terminal.VariableOwner; | |||
import com.vaadin.ui.Alignment; | |||
@@ -1111,10 +1111,13 @@ public class JsonPaintTarget implements PaintTarget { | |||
return usedPaintableTypes; | |||
} | |||
public void addVariable(VariableOwner owner, String name, StreamVariable value) | |||
throws PaintException { | |||
String url = manager.createStreamVariableTargetUrl(owner, name, value); | |||
addVariable(owner, name, url); | |||
public void addVariable(VariableOwner owner, String name, | |||
StreamVariable value) throws PaintException { | |||
String url = manager.getStreamVariableTargetUrl(owner, name, value); | |||
if (url != null) { | |||
addVariable(owner, name, url); | |||
} // else { //NOP this was just a cleanup by component } | |||
} | |||
} |
@@ -197,21 +197,18 @@ public class PortletCommunicationManager extends AbstractCommunicationManager { | |||
String contentType = request.getContentType(); | |||
String name = request.getParameter("name"); | |||
String ownerId = request.getParameter("rec-owner"); | |||
VariableOwner variableOwner = (VariableOwner) getVariableOwner(ownerId); | |||
StreamVariable streamVariable = ownerToNameToStreamVariable.get(variableOwner).remove( | |||
name); | |||
// clean up, may be re added on next paint | |||
ownerToNameToStreamVariable.get(variableOwner).remove(name); | |||
VariableOwner variableOwner = getVariableOwner(ownerId); | |||
StreamVariable streamVariable = ownerToNameToStreamVariable.get( | |||
variableOwner).get(name); | |||
if (contentType.contains("boundary")) { | |||
doHandleSimpleMultipartFileUpload( | |||
new PortletRequestWrapper(request), | |||
new PortletResponseWrapper(response), streamVariable, | |||
new PortletResponseWrapper(response), streamVariable, name, | |||
variableOwner, contentType.split("boundary=")[1]); | |||
} else { | |||
doHandleXhrFilePost(new PortletRequestWrapper(request), | |||
new PortletResponseWrapper(response), streamVariable, | |||
new PortletResponseWrapper(response), streamVariable, name, | |||
variableOwner, request.getContentLength()); | |||
} | |||
@@ -274,11 +271,13 @@ public class PortletCommunicationManager extends AbstractCommunicationManager { | |||
private Map<VariableOwner, Map<String, StreamVariable>> ownerToNameToStreamVariable; | |||
@Override | |||
String createStreamVariableTargetUrl(VariableOwner owner, String name, StreamVariable value) { | |||
String getStreamVariableTargetUrl(VariableOwner owner, String name, | |||
StreamVariable value) { | |||
if (ownerToNameToStreamVariable == null) { | |||
ownerToNameToStreamVariable = new HashMap<VariableOwner, Map<String, StreamVariable>>(); | |||
} | |||
Map<String, StreamVariable> nameToReceiver = ownerToNameToStreamVariable.get(owner); | |||
Map<String, StreamVariable> nameToReceiver = ownerToNameToStreamVariable | |||
.get(owner); | |||
if (nameToReceiver == null) { | |||
nameToReceiver = new HashMap<String, StreamVariable>(); | |||
ownerToNameToStreamVariable.put(owner, nameToReceiver); | |||
@@ -293,4 +292,14 @@ public class PortletCommunicationManager extends AbstractCommunicationManager { | |||
return resurl.toString(); | |||
} | |||
@Override | |||
protected void cleanStreamVariable(VariableOwner owner, String name) { | |||
Map<String, StreamVariable> map = ownerToNameToStreamVariable | |||
.get(owner); | |||
map.remove(name); | |||
if (map.isEmpty()) { | |||
ownerToNameToStreamVariable.remove(owner); | |||
} | |||
} | |||
} |
@@ -6,9 +6,19 @@ import com.vaadin.terminal.StreamVariable.StreamingStartEvent; | |||
final class StreamingStartEventImpl extends AbstractStreamingEvent implements | |||
StreamingStartEvent { | |||
private boolean disposed; | |||
public StreamingStartEventImpl(final String filename, final String type, | |||
long contentLength) { | |||
super(filename, type, contentLength, 0); | |||
} | |||
public void disposeStreamVariable() { | |||
disposed = true; | |||
} | |||
boolean isDisposed() { | |||
return disposed; | |||
} | |||
} |
@@ -256,6 +256,9 @@ public class DragAndDropWrapper extends CustomComponent implements DropTarget, | |||
} | |||
// no need tell to the client about this receiver on next paint | |||
receivers.remove(file); | |||
// let the terminal GC the streamvariable and not to accept other | |||
// file uploads to this variable | |||
event.disposeStreamVariable(); | |||
} | |||
public void streamingFinished(StreamingEndEvent event) { | |||
@@ -317,6 +320,14 @@ public class DragAndDropWrapper extends CustomComponent implements DropTarget, | |||
public long getBytesReceived() { | |||
return wrappedEvent.getBytesReceived(); | |||
} | |||
/** | |||
* Calling this method has no effect. DD files are receive only once | |||
* anyway. | |||
*/ | |||
public void disposeStreamVariable() { | |||
} | |||
} | |||
} |