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.
*
* 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
* @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
* {@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.
}
/**
- * 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 {
}
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;
*/
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
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,
* @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
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,
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) {
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();
throw new UploadException(e);
}
}
+ return startedEvent.isDisposed();
}
private void tryToCloseStream(OutputStream out) {
}
- 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);
+
}
/**
* Handles file upload request submitted via Upload component.
*
- * @see #createStreamVariableTargetUrl(ReceiverOwner, String,
- * StreamVariable)
+ * @see #getStreamVariableTargetUrl(ReceiverOwner, String, StreamVariable)
*
* @param request
* @param response
+ 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(
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
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 + "/"
}
+ @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));
+ }
+ }
+
}
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;
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 }
+
}
}
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());
}
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);
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);
+ }
+ }
+
}
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;
+ }
+
}
}
// 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) {
public long getBytesReceived() {
return wrappedEvent.getBytesReceived();
}
+
+ /**
+ * Calling this method has no effect. DD files are receive only once
+ * anyway.
+ */
+ public void disposeStreamVariable() {
+
+ }
}
}