12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204 |
- /*
- @VaadinApache2LicenseForJavaFiles@
- */
-
- package com.vaadin.terminal.gwt.server;
-
- import java.io.BufferedWriter;
- import java.io.ByteArrayOutputStream;
- import java.io.CharArrayWriter;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.InputStreamReader;
- import java.io.OutputStream;
- import java.io.OutputStreamWriter;
- import java.io.PrintWriter;
- import java.io.Serializable;
- import java.io.StringWriter;
- import java.lang.reflect.InvocationTargetException;
- import java.lang.reflect.Method;
- import java.security.GeneralSecurityException;
- import java.text.CharacterIterator;
- import java.text.DateFormat;
- import java.text.DateFormatSymbols;
- import java.text.SimpleDateFormat;
- import java.text.StringCharacterIterator;
- import java.util.ArrayList;
- import java.util.Calendar;
- import java.util.Collection;
- import java.util.Collections;
- import java.util.Comparator;
- import java.util.GregorianCalendar;
- import java.util.HashMap;
- import java.util.HashSet;
- import java.util.Iterator;
- import java.util.LinkedList;
- import java.util.List;
- import java.util.Locale;
- import java.util.Map;
- import java.util.Set;
- import java.util.StringTokenizer;
- import java.util.UUID;
- import java.util.logging.Level;
- import java.util.logging.Logger;
-
- import com.vaadin.Application;
- import com.vaadin.Application.SystemMessages;
- import com.vaadin.RootRequiresMoreInformationException;
- import com.vaadin.external.json.JSONException;
- import com.vaadin.external.json.JSONObject;
- import com.vaadin.terminal.CombinedRequest;
- import com.vaadin.terminal.PaintException;
- import com.vaadin.terminal.PaintTarget;
- import com.vaadin.terminal.Paintable;
- import com.vaadin.terminal.Paintable.RepaintRequestEvent;
- import com.vaadin.terminal.RequestHandler;
- import com.vaadin.terminal.StreamVariable;
- import com.vaadin.terminal.StreamVariable.StreamingEndEvent;
- import com.vaadin.terminal.StreamVariable.StreamingErrorEvent;
- import com.vaadin.terminal.Terminal.ErrorEvent;
- import com.vaadin.terminal.Terminal.ErrorListener;
- import com.vaadin.terminal.VariableOwner;
- import com.vaadin.terminal.WrappedRequest;
- import com.vaadin.terminal.WrappedResponse;
- import com.vaadin.terminal.gwt.client.ApplicationConnection;
- import com.vaadin.terminal.gwt.server.BootstrapHandler.BootstrapContext;
- import com.vaadin.terminal.gwt.server.ComponentSizeValidator.InvalidLayout;
- import com.vaadin.ui.AbstractComponent;
- import com.vaadin.ui.AbstractField;
- import com.vaadin.ui.Component;
- import com.vaadin.ui.Root;
-
- /**
- * This is a common base class for the server-side implementations of the
- * communication system between the client code (compiled with GWT into
- * JavaScript) and the server side components. Its client side counterpart is
- * {@link ApplicationConnection}.
- *
- * A server side component sends its state to the client in a paint request (see
- * {@link Paintable} and {@link PaintTarget} on the server side). The client
- * widget receives these paint requests as calls to
- * {@link com.vaadin.terminal.gwt.client.VPaintableWidget#updateFromUIDL()}. The client
- * component communicates back to the server by sending a list of variable
- * changes (see {@link ApplicationConnection#updateVariable()} and
- * {@link VariableOwner#changeVariables(Object, Map)}).
- *
- * TODO Document better!
- */
- @SuppressWarnings("serial")
- public abstract class AbstractCommunicationManager implements
- Paintable.RepaintRequestListener, Serializable {
-
- private static final String DASHDASH = "--";
-
- private static final Logger logger = Logger
- .getLogger(AbstractCommunicationManager.class.getName());
-
- private static final RequestHandler APP_RESOURCE_HANDLER = new ApplicationResourceHandler();
-
- private static final RequestHandler UNSUPPORTED_BROWSER_HANDLER = new UnsupportedBrowserHandler();
-
- /**
- * TODO Document me!
- *
- * @author peholmst
- */
- public interface Callback extends Serializable {
-
- public void criticalNotification(WrappedRequest request,
- WrappedResponse response, String cap, String msg,
- String details, String outOfSyncURL) throws IOException;
- }
-
- static class UploadInterruptedException extends Exception {
- public UploadInterruptedException() {
- super("Upload interrupted by other thread");
- }
- }
-
- private static String GET_PARAM_REPAINT_ALL = "repaintAll";
-
- // flag used in the request to indicate that the security token should be
- // written to the response
- private static final String WRITE_SECURITY_TOKEN_FLAG = "writeSecurityToken";
-
- /* Variable records indexes */
- private static final int VAR_PID = 1;
- private static final int VAR_NAME = 2;
- private static final int VAR_TYPE = 3;
- private static final int VAR_VALUE = 0;
-
- private static final char VTYPE_PAINTABLE = 'p';
- private static final char VTYPE_BOOLEAN = 'b';
- private static final char VTYPE_DOUBLE = 'd';
- private static final char VTYPE_FLOAT = 'f';
- private static final char VTYPE_LONG = 'l';
- private static final char VTYPE_INTEGER = 'i';
- private static final char VTYPE_STRING = 's';
- private static final char VTYPE_ARRAY = 'a';
- private static final char VTYPE_STRINGARRAY = 'c';
- private static final char VTYPE_MAP = 'm';
-
- private static final char VAR_RECORD_SEPARATOR = '\u001e';
-
- private static final char VAR_FIELD_SEPARATOR = '\u001f';
-
- public static final char VAR_BURST_SEPARATOR = '\u001d';
-
- public static final char VAR_ARRAYITEM_SEPARATOR = '\u001c';
-
- public static final char VAR_ESCAPE_CHARACTER = '\u001b';
-
- private final HashMap<Integer, OpenWindowCache> currentlyOpenWindowsInClient = new HashMap<Integer, OpenWindowCache>();
-
- private static final int MAX_BUFFER_SIZE = 64 * 1024;
-
- /* Same as in apache commons file upload library that was previously used. */
- private static final int MAX_UPLOAD_BUFFER_SIZE = 4 * 1024;
-
- private static final String GET_PARAM_ANALYZE_LAYOUTS = "analyzeLayouts";
-
- private final ArrayList<Paintable> dirtyPaintables = new ArrayList<Paintable>();
-
- private final HashMap<Paintable, String> paintableIdMap = new HashMap<Paintable, String>();
-
- private final HashMap<String, Paintable> idPaintableMap = new HashMap<String, Paintable>();
-
- private int idSequence = 0;
-
- private final Application application;
-
- private List<String> locales;
-
- private int pendingLocalesIndex;
-
- private int timeoutInterval = -1;
-
- private DragAndDropService dragAndDropService;
-
- private String requestThemeName;
-
- private int maxInactiveInterval;
-
- /**
- * TODO New constructor - document me!
- *
- * @param application
- */
- public AbstractCommunicationManager(Application application) {
- this.application = application;
- application.addRequestHandler(getBootstrapHandler());
- application.addRequestHandler(APP_RESOURCE_HANDLER);
- application.addRequestHandler(UNSUPPORTED_BROWSER_HANDLER);
- requireLocale(application.getLocale().toString());
- }
-
- protected Application getApplication() {
- return application;
- }
-
- private static final int LF = "\n".getBytes()[0];
-
- private static final String CRLF = "\r\n";
-
- private static final String UTF8 = "UTF8";
-
- private static final String GET_PARAM_HIGHLIGHT_COMPONENT = "highlightComponent";
-
- private Paintable highLightedPaintable;
-
- private static String readLine(InputStream stream) throws IOException {
- ByteArrayOutputStream bout = new ByteArrayOutputStream();
- int readByte = stream.read();
- while (readByte != LF) {
- bout.write(readByte);
- readByte = stream.read();
- }
- byte[] bytes = bout.toByteArray();
- return new String(bytes, 0, bytes.length - 1, UTF8);
- }
-
- /**
- * Method used to stream content from a multipart request (either from
- * servlet or portlet request) to given StreamVariable
- *
- *
- * @param request
- * @param response
- * @param streamVariable
- * @param owner
- * @param boundary
- * @throws IOException
- */
- protected void doHandleSimpleMultipartFileUpload(WrappedRequest request,
- WrappedResponse response, StreamVariable streamVariable,
- String variableName, VariableOwner owner, String boundary)
- throws IOException {
- // multipart parsing, supports only one file for request, but that is
- // fine for our current terminal
-
- final InputStream inputStream = request.getInputStream();
-
- int contentLength = request.getContentLength();
-
- boolean atStart = false;
- boolean firstFileFieldFound = false;
-
- String rawfilename = "unknown";
- String rawMimeType = "application/octet-stream";
-
- /*
- * Read the stream until the actual file starts (empty line). Read
- * filename and content type from multipart headers.
- */
- while (!atStart) {
- String readLine = readLine(inputStream);
- contentLength -= (readLine.length() + 2);
- if (readLine.startsWith("Content-Disposition:")
- && readLine.indexOf("filename=") > 0) {
- rawfilename = readLine.replaceAll(".*filename=", "");
- String parenthesis = rawfilename.substring(0, 1);
- rawfilename = rawfilename.substring(1);
- rawfilename = rawfilename.substring(0,
- rawfilename.indexOf(parenthesis));
- firstFileFieldFound = true;
- } else if (firstFileFieldFound && readLine.equals("")) {
- atStart = true;
- } else if (readLine.startsWith("Content-Type")) {
- rawMimeType = readLine.split(": ")[1];
- }
- }
-
- contentLength -= (boundary.length() + CRLF.length() + 2
- * DASHDASH.length() + 2); // 2 == CRLF
-
- /*
- * Reads bytes from the underlying stream. Compares the read bytes to
- * the boundary string and returns -1 if met.
- *
- * The matching happens so that if the read byte equals to the first
- * char of boundary string, the stream goes to "buffering mode". In
- * buffering mode bytes are read until the character does not match the
- * corresponding from boundary string or the full boundary string is
- * found.
- *
- * Note, if this is someday needed elsewhere, don't shoot yourself to
- * foot and split to a top level helper class.
- */
- InputStream simpleMultiPartReader = new SimpleMultiPartInputStream(
- inputStream, boundary);
-
- /*
- * Should report only the filename even if the browser sends the path
- */
- final String filename = removePath(rawfilename);
- final String mimeType = rawMimeType;
-
- try {
- /*
- * safe cast as in GWT terminal all variable owners are expected to
- * be components.
- */
- Component component = (Component) owner;
- if (component.isReadOnly()) {
- throw new UploadException(
- "Warning: file upload ignored because the componente was read-only");
- }
- boolean forgetVariable = streamToReceiver(simpleMultiPartReader,
- streamVariable, filename, mimeType, contentLength);
- if (forgetVariable) {
- cleanStreamVariable(owner, variableName);
- }
- } catch (Exception e) {
- synchronized (application) {
- handleChangeVariablesError(application, (Component) owner, e,
- new HashMap<String, Object>());
- }
- }
- sendUploadResponse(request, response);
-
- }
-
- /**
- * Used to stream plain file post (aka XHR2.post(File))
- *
- * @param request
- * @param response
- * @param streamVariable
- * @param owner
- * @param contentLength
- * @throws IOException
- */
- protected void doHandleXhrFilePost(WrappedRequest request,
- WrappedResponse response, 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
- final String filename = "unknown";
- final String mimeType = filename;
- final InputStream stream = request.getInputStream();
- try {
- /*
- * safe cast as in GWT terminal all variable owners are expected to
- * be components.
- */
- Component component = (Component) owner;
- if (component.isReadOnly()) {
- throw new UploadException(
- "Warning: file upload ignored because the component was read-only");
- }
- boolean forgetVariable = streamToReceiver(stream, streamVariable,
- filename, mimeType, contentLength);
- if (forgetVariable) {
- cleanStreamVariable(owner, variableName);
- }
- } catch (Exception e) {
- synchronized (application) {
- handleChangeVariablesError(application, (Component) owner, e,
- new HashMap<String, Object>());
- }
- }
- sendUploadResponse(request, response);
- }
-
- /**
- * @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) {
- throw new IllegalStateException(
- "StreamVariable for the post not found");
- }
-
- final Application application = getApplication();
-
- OutputStream out = null;
- int totalBytes = 0;
- StreamingStartEventImpl startedEvent = new StreamingStartEventImpl(
- filename, type, contentLength);
- try {
- boolean listenProgress;
- synchronized (application) {
- streamVariable.streamingStarted(startedEvent);
- out = streamVariable.getOutputStream();
- listenProgress = streamVariable.listenProgress();
- }
-
- // Gets the output target stream
- if (out == null) {
- throw new NoOutputStreamException();
- }
-
- if (null == in) {
- // No file, for instance non-existent filename in html upload
- throw new NoInputStreamException();
- }
-
- final byte buffer[] = new byte[MAX_UPLOAD_BUFFER_SIZE];
- int bytesReadToBuffer = 0;
- while ((bytesReadToBuffer = in.read(buffer)) > 0) {
- out.write(buffer, 0, bytesReadToBuffer);
- totalBytes += bytesReadToBuffer;
- if (listenProgress) {
- // update progress if listener set and contentLength
- // received
- synchronized (application) {
- StreamingProgressEventImpl progressEvent = new StreamingProgressEventImpl(
- filename, type, contentLength, totalBytes);
- streamVariable.onProgress(progressEvent);
- }
- }
- if (streamVariable.isInterrupted()) {
- throw new UploadInterruptedException();
- }
- }
-
- // upload successful
- out.close();
- StreamingEndEvent event = new StreamingEndEventImpl(filename, type,
- totalBytes);
- synchronized (application) {
- streamVariable.streamingFinished(event);
- }
-
- } catch (UploadInterruptedException e) {
- // Download interrupted by application code
- tryToCloseStream(out);
- StreamingErrorEvent event = new StreamingErrorEventImpl(filename,
- type, contentLength, totalBytes, e);
- synchronized (application) {
- streamVariable.streamingFailed(event);
- }
- // Note, we are not throwing interrupted exception forward as it is
- // not a terminal level error like all other exception.
- } catch (final Exception e) {
- tryToCloseStream(out);
- synchronized (application) {
- StreamingErrorEvent event = new StreamingErrorEventImpl(
- filename, type, contentLength, totalBytes, e);
- synchronized (application) {
- streamVariable.streamingFailed(event);
- }
- // throw exception for terminal to be handled (to be passed to
- // terminalErrorHandler)
- throw new UploadException(e);
- }
- }
- return startedEvent.isDisposed();
- }
-
- static void tryToCloseStream(OutputStream out) {
- try {
- // try to close output stream (e.g. file handle)
- if (out != null) {
- out.close();
- }
- } catch (IOException e1) {
- // NOP
- }
- }
-
- /**
- * Removes any possible path information from the filename and returns the
- * filename. Separators / and \\ are used.
- *
- * @param name
- * @return
- */
- private static String removePath(String filename) {
- if (filename != null) {
- filename = filename.replaceAll("^.*[/\\\\]", "");
- }
-
- return filename;
- }
-
- /**
- * TODO document
- *
- * @param request
- * @param response
- * @throws IOException
- */
- protected void sendUploadResponse(WrappedRequest request,
- WrappedResponse response) throws IOException {
- response.setContentType("text/html");
- final OutputStream out = response.getOutputStream();
- final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
- new OutputStreamWriter(out, "UTF-8")));
- outWriter.print("<html><body>download handled</body></html>");
- outWriter.flush();
- out.close();
- }
-
- /**
- * Internally process a UIDL request from the client.
- *
- * This method calls
- * {@link #handleVariables(WrappedRequest, WrappedResponse, Callback, Application, Root)}
- * to process any changes to variables by the client and then repaints
- * affected components using {@link #paintAfterVariableChanges()}.
- *
- * Also, some cleanup is done when a request arrives for an application that
- * has already been closed.
- *
- * The method handleUidlRequest(...) in subclasses should call this method.
- *
- * TODO better documentation
- *
- * @param request
- * @param response
- * @param callback
- * @param root
- * target window for the UIDL request, can be null if target not
- * found
- * @throws IOException
- * @throws InvalidUIDLSecurityKeyException
- */
- public void handleUidlRequest(WrappedRequest request,
- WrappedResponse response, Callback callback, Root root)
- throws IOException, InvalidUIDLSecurityKeyException {
-
- requestThemeName = request.getParameter("theme");
- maxInactiveInterval = request.getSessionMaxInactiveInterval();
- // repaint requested or session has timed out and new one is created
- boolean repaintAll;
- final OutputStream out;
-
- repaintAll = (request.getParameter(GET_PARAM_REPAINT_ALL) != null);
- // || (request.getSession().isNew()); FIXME What the h*ll is this??
- out = response.getOutputStream();
-
- boolean analyzeLayouts = false;
- if (repaintAll) {
- // analyzing can be done only with repaintAll
- analyzeLayouts = (request.getParameter(GET_PARAM_ANALYZE_LAYOUTS) != null);
-
- if (request.getParameter(GET_PARAM_HIGHLIGHT_COMPONENT) != null) {
- String pid = request
- .getParameter(GET_PARAM_HIGHLIGHT_COMPONENT);
- highLightedPaintable = idPaintableMap.get(pid);
- highlightPaintable(highLightedPaintable);
- }
- }
-
- final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
- new OutputStreamWriter(out, "UTF-8")));
-
- // The rest of the process is synchronized with the application
- // in order to guarantee that no parallel variable handling is
- // made
- synchronized (application) {
-
- // Finds the window within the application
- if (application.isRunning()) {
- // Returns if no window found
- if (root == null) {
- // This should not happen, no windows exists but
- // application is still open.
- logger.warning("Could not get root for application");
- return;
- }
- } else {
- // application has been closed
- endApplication(request, response, application);
- return;
- }
-
- // Change all variables based on request parameters
- if (!handleVariables(request, response, callback, application, root)) {
-
- // var inconsistency; the client is probably out-of-sync
- SystemMessages ci = null;
- try {
- Method m = application.getClass().getMethod(
- "getSystemMessages", (Class[]) null);
- ci = (Application.SystemMessages) m.invoke(null,
- (Object[]) null);
- } catch (Exception e2) {
- // FIXME: Handle exception
- // Not critical, but something is still wrong; print
- // stacktrace
- logger.log(Level.WARNING,
- "getSystemMessages() failed - continuing", e2);
- }
- if (ci != null) {
- String msg = ci.getOutOfSyncMessage();
- String cap = ci.getOutOfSyncCaption();
- if (msg != null || cap != null) {
- callback.criticalNotification(request, response, cap,
- msg, null, ci.getOutOfSyncURL());
- // will reload page after this
- return;
- }
- }
- // No message to show, let's just repaint all.
- repaintAll = true;
- }
-
- paintAfterVariableChanges(request, response, callback, repaintAll,
- outWriter, root, analyzeLayouts);
-
- }
-
- outWriter.close();
- requestThemeName = null;
- }
-
- protected void highlightPaintable(Paintable highLightedPaintable2) {
- StringBuilder sb = new StringBuilder();
- sb.append("*** Debug details of a component: *** \n");
- sb.append("Type: ");
- sb.append(highLightedPaintable2.getClass().getName());
- if (highLightedPaintable2 instanceof AbstractComponent) {
- AbstractComponent component = (AbstractComponent) highLightedPaintable2;
- sb.append("\nId:");
- sb.append(paintableIdMap.get(component));
- if (component.getCaption() != null) {
- sb.append("\nCaption:");
- sb.append(component.getCaption());
- }
-
- printHighlightedComponentHierarchy(sb, component);
- }
- logger.info(sb.toString());
- }
-
- protected void printHighlightedComponentHierarchy(StringBuilder sb,
- AbstractComponent component) {
- LinkedList<Component> h = new LinkedList<Component>();
- h.add(component);
- Component parent = component.getParent();
- while (parent != null) {
- h.addFirst(parent);
- parent = parent.getParent();
- }
-
- sb.append("\nComponent hierarchy:\n");
- Application application2 = component.getApplication();
- sb.append(application2.getClass().getName());
- sb.append(".");
- sb.append(application2.getClass().getSimpleName());
- sb.append("(");
- sb.append(application2.getClass().getSimpleName());
- sb.append(".java");
- sb.append(":1)");
- int l = 1;
- for (Component component2 : h) {
- sb.append("\n");
- for (int i = 0; i < l; i++) {
- sb.append(" ");
- }
- l++;
- Class<? extends Component> componentClass = component2.getClass();
- Class<?> topClass = componentClass;
- while (topClass.getEnclosingClass() != null) {
- topClass = topClass.getEnclosingClass();
- }
- sb.append(componentClass.getName());
- sb.append(".");
- sb.append(componentClass.getSimpleName());
- sb.append("(");
- sb.append(topClass.getSimpleName());
- sb.append(".java:1)");
- }
- }
-
- /**
- * TODO document
- *
- * @param request
- * @param response
- * @param callback
- * @param repaintAll
- * @param outWriter
- * @param window
- * @param analyzeLayouts
- * @throws PaintException
- * @throws IOException
- */
- private void paintAfterVariableChanges(WrappedRequest request,
- WrappedResponse response, Callback callback, boolean repaintAll,
- final PrintWriter outWriter, Root root, boolean analyzeLayouts)
- throws PaintException, IOException {
-
- if (repaintAll) {
- makeAllPaintablesDirty(root);
- }
-
- // Removes application if it has stopped during variable changes
- if (!application.isRunning()) {
- endApplication(request, response, application);
- return;
- }
-
- openJsonMessage(outWriter, response);
-
- // security key
- Object writeSecurityTokenFlag = request
- .getAttribute(WRITE_SECURITY_TOKEN_FLAG);
-
- if (writeSecurityTokenFlag != null) {
- outWriter.print(getSecurityKeyUIDL(request));
- }
-
- writeUidlResponce(repaintAll, outWriter, root, analyzeLayouts);
-
- closeJsonMessage(outWriter);
-
- outWriter.close();
-
- }
-
- /**
- * Gets the security key (and generates one if needed) as UIDL.
- *
- * @param request
- * @return the security key UIDL or "" if the feature is turned off
- */
- public String getSecurityKeyUIDL(WrappedRequest request) {
- final String seckey = getSecurityKey(request);
- if (seckey != null) {
- return "\"" + ApplicationConnection.UIDL_SECURITY_TOKEN_ID
- + "\":\"" + seckey + "\",";
- } else {
- return "";
- }
- }
-
- /**
- * Gets the security key (and generates one if needed).
- *
- * @param request
- * @return the security key
- */
- protected String getSecurityKey(WrappedRequest request) {
- String seckey = null;
- seckey = (String) request
- .getSessionAttribute(ApplicationConnection.UIDL_SECURITY_TOKEN_ID);
- if (seckey == null) {
- seckey = UUID.randomUUID().toString();
- request.setSessionAttribute(
- ApplicationConnection.UIDL_SECURITY_TOKEN_ID, seckey);
- }
-
- return seckey;
- }
-
- public void writeUidlResponce(boolean repaintAll,
- final PrintWriter outWriter, Root root, boolean analyzeLayouts)
- throws PaintException {
- outWriter.print("\"changes\":[");
-
- ArrayList<Paintable> paintables = null;
-
- List<InvalidLayout> invalidComponentRelativeSizes = null;
-
- JsonPaintTarget paintTarget = new JsonPaintTarget(this, outWriter,
- !repaintAll);
- OpenWindowCache windowCache = currentlyOpenWindowsInClient.get(Integer
- .valueOf(root.getRootId()));
- if (windowCache == null) {
- windowCache = new OpenWindowCache();
- currentlyOpenWindowsInClient.put(Integer.valueOf(root.getRootId()),
- windowCache);
- }
-
- // Paints components
- if (repaintAll) {
- paintables = new ArrayList<Paintable>();
- paintables.add(root);
-
- // Reset sent locales
- locales = null;
- requireLocale(application.getLocale().toString());
-
- } else {
- // remove detached components from paintableIdMap so they
- // can be GC'ed
- /*
- * TODO figure out if we could move this beyond the painting phase,
- * "respond as fast as possible, then do the cleanup". Beware of
- * painting the dirty detatched components.
- */
- for (Iterator<Paintable> it = paintableIdMap.keySet().iterator(); it
- .hasNext();) {
- Component p = (Component) it.next();
- if (p.getApplication() == null) {
- unregisterPaintable(p);
- // Take into account that some other component may have
- // reused p's ID by now (this can happen when manually
- // assigning IDs with setDebugId().) See #8090.
- String pid = paintableIdMap.get(p);
- if (idPaintableMap.get(pid) == p) {
- idPaintableMap.remove(pid);
- }
- it.remove();
- dirtyPaintables.remove(p);
- }
- }
- paintables = getDirtyVisibleComponents(root);
- }
- if (paintables != null) {
-
- // We need to avoid painting children before parent.
- // This is ensured by ordering list by depth in component
- // tree
- Collections.sort(paintables, new Comparator<Paintable>() {
- public int compare(Paintable o1, Paintable o2) {
- Component c1 = (Component) o1;
- Component c2 = (Component) o2;
- int d1 = 0;
- while (c1.getParent() != null) {
- d1++;
- c1 = c1.getParent();
- }
- int d2 = 0;
- while (c2.getParent() != null) {
- d2++;
- c2 = c2.getParent();
- }
- if (d1 < d2) {
- return -1;
- }
- if (d1 > d2) {
- return 1;
- }
- return 0;
- }
- });
-
- for (final Iterator<Paintable> i = paintables.iterator(); i
- .hasNext();) {
- final Paintable p = i.next();
-
- // // TODO CLEAN
- // if (p instanceof Root) {
- // final Root r = (Root) p;
- // if (r.getTerminal() == null) {
- // r.setTerminal(application.getRoot().getTerminal());
- // }
- // }
- /*
- * This does not seem to happen in tk5, but remember this case:
- * else if (p instanceof Component) { if (((Component)
- * p).getParent() == null || ((Component) p).getApplication() ==
- * null) { // Component requested repaint, but is no // longer
- * attached: skip paintablePainted(p); continue; } }
- */
-
- // TODO we may still get changes that have been
- // rendered already (changes with only cached flag)
- if (paintTarget.needsToBePainted(p)) {
- paintTarget.startTag("change");
- paintTarget.addAttribute("format", "uidl");
- final String pid = getPaintableId(p);
- paintTarget.addAttribute("pid", pid);
-
- p.paint(paintTarget);
-
- paintTarget.endTag("change");
- }
- paintablePainted(p);
-
- if (analyzeLayouts) {
- Root w = (Root) p;
- invalidComponentRelativeSizes = ComponentSizeValidator
- .validateComponentRelativeSizes(w.getContent(),
- null, null);
-
- // // Also check any existing subwindows
- // if (w.getChildWindows() != null) {
- // for (Window subWindow : w.getChildWindows()) {
- // invalidComponentRelativeSizes = ComponentSizeValidator
- // .validateComponentRelativeSizes(
- // subWindow.getContent(),
- // invalidComponentRelativeSizes, null);
- // }
- // }
- }
- }
- }
-
- paintTarget.close();
- outWriter.print("]"); // close changes
-
- outWriter.print(", \"meta\" : {");
- boolean metaOpen = false;
-
- if (repaintAll) {
- metaOpen = true;
- outWriter.write("\"repaintAll\":true");
- if (analyzeLayouts) {
- outWriter.write(", \"invalidLayouts\":");
- outWriter.write("[");
- if (invalidComponentRelativeSizes != null) {
- boolean first = true;
- for (InvalidLayout invalidLayout : invalidComponentRelativeSizes) {
- if (!first) {
- outWriter.write(",");
- } else {
- first = false;
- }
- invalidLayout.reportErrors(outWriter, this, System.err);
- }
- }
- outWriter.write("]");
- }
- if (highLightedPaintable != null) {
- outWriter.write(", \"hl\":\"");
- outWriter.write(paintableIdMap.get(highLightedPaintable));
- outWriter.write("\"");
- highLightedPaintable = null;
- }
- }
-
- SystemMessages ci = null;
- try {
- Method m = application.getClass().getMethod("getSystemMessages",
- (Class[]) null);
- ci = (Application.SystemMessages) m.invoke(null, (Object[]) null);
- } catch (NoSuchMethodException e) {
- logger.log(Level.WARNING,
- "getSystemMessages() failed - continuing", e);
- } catch (IllegalArgumentException e) {
- logger.log(Level.WARNING,
- "getSystemMessages() failed - continuing", e);
- } catch (IllegalAccessException e) {
- logger.log(Level.WARNING,
- "getSystemMessages() failed - continuing", e);
- } catch (InvocationTargetException e) {
- logger.log(Level.WARNING,
- "getSystemMessages() failed - continuing", e);
- }
-
- // meta instruction for client to enable auto-forward to
- // sessionExpiredURL after timer expires.
- if (ci != null && ci.getSessionExpiredMessage() == null
- && ci.getSessionExpiredCaption() == null
- && ci.isSessionExpiredNotificationEnabled()) {
- int newTimeoutInterval = getTimeoutInterval();
- if (repaintAll || (timeoutInterval != newTimeoutInterval)) {
- String escapedURL = ci.getSessionExpiredURL() == null ? "" : ci
- .getSessionExpiredURL().replace("/", "\\/");
- if (metaOpen) {
- outWriter.write(",");
- }
- outWriter.write("\"timedRedirect\":{\"interval\":"
- + (newTimeoutInterval + 15) + ",\"url\":\""
- + escapedURL + "\"}");
- metaOpen = true;
- }
- timeoutInterval = newTimeoutInterval;
- }
-
- outWriter.print("}, \"resources\" : {");
-
- // Precache custom layouts
-
- // TODO We should only precache the layouts that are not
- // cached already (plagiate from usedPaintableTypes)
- int resourceIndex = 0;
- for (final Iterator<Object> i = paintTarget.getUsedResources()
- .iterator(); i.hasNext();) {
- final String resource = (String) i.next();
- InputStream is = null;
- try {
- is = getThemeResourceAsStream(root, getTheme(root), resource);
- } catch (final Exception e) {
- // FIXME: Handle exception
- logger.log(Level.FINER, "Failed to get theme resource stream.",
- e);
- }
- if (is != null) {
-
- outWriter.print((resourceIndex++ > 0 ? ", " : "") + "\""
- + resource + "\" : ");
- final StringBuffer layout = new StringBuffer();
-
- try {
- final InputStreamReader r = new InputStreamReader(is,
- "UTF-8");
- final char[] buffer = new char[20000];
- int charsRead = 0;
- while ((charsRead = r.read(buffer)) > 0) {
- layout.append(buffer, 0, charsRead);
- }
- r.close();
- } catch (final java.io.IOException e) {
- // FIXME: Handle exception
- logger.log(Level.INFO, "Resource transfer failed", e);
- }
- outWriter.print("\""
- + JsonPaintTarget.escapeJSON(layout.toString()) + "\"");
- } else {
- // FIXME: Handle exception
- logger.severe("CustomLayout not found: " + resource);
- }
- }
- outWriter.print("}");
-
- Collection<Class<? extends Paintable>> usedPaintableTypes = paintTarget
- .getUsedPaintableTypes();
- boolean typeMappingsOpen = false;
- for (Class<? extends Paintable> class1 : usedPaintableTypes) {
- if (windowCache.cache(class1)) {
- // client does not know the mapping key for this type, send
- // mapping to client
- if (!typeMappingsOpen) {
- typeMappingsOpen = true;
- outWriter.print(", \"typeMappings\" : { ");
- } else {
- outWriter.print(" , ");
- }
- String canonicalName = class1.getCanonicalName();
- outWriter.print("\"");
- outWriter.print(canonicalName);
- outWriter.print("\" : ");
- outWriter.print(getTagForType(class1));
- }
- }
- if (typeMappingsOpen) {
- outWriter.print(" }");
- }
-
- // add any pending locale definitions requested by the client
- printLocaleDeclarations(outWriter);
-
- if (dragAndDropService != null) {
- dragAndDropService.printJSONResponse(outWriter);
- }
- }
-
- protected abstract InputStream getThemeResourceAsStream(Root root,
- String themeName, String resource);
-
- private int getTimeoutInterval() {
- return maxInactiveInterval;
- }
-
- private String getTheme(Root root) {
- String themeName = root.getApplication().getThemeForRoot(root);
- String requestThemeName = getRequestTheme();
-
- if (requestThemeName != null) {
- themeName = requestThemeName;
- }
- if (themeName == null) {
- themeName = AbstractApplicationServlet.getDefaultTheme();
- }
- return themeName;
- }
-
- private String getRequestTheme() {
- return requestThemeName;
- }
-
- public void makeAllPaintablesDirty(Root root) {
- // If repaint is requested, clean all ids in this root window
- for (final Iterator<String> it = idPaintableMap.keySet().iterator(); it
- .hasNext();) {
- final Component c = (Component) idPaintableMap.get(it.next());
- if (isChildOf(root, c)) {
- it.remove();
- paintableIdMap.remove(c);
- }
- }
- // clean WindowCache
- OpenWindowCache openWindowCache = currentlyOpenWindowsInClient
- .get(Integer.valueOf(root.getRootId()));
- if (openWindowCache != null) {
- openWindowCache.clear();
- }
- }
-
- /**
- * Called when communication manager stops listening for repaints for given
- * component.
- *
- * @param p
- */
- protected void unregisterPaintable(Component p) {
- p.removeListener(this);
- }
-
- /**
- * Returns false if the cross site request forgery protection is turned off.
- *
- * @param application
- * @return false if the XSRF is turned off, true otherwise
- */
- public boolean isXSRFEnabled(Application application) {
- return !"true"
- .equals(application
- .getProperty(AbstractApplicationServlet.SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION));
- }
-
- /**
- * TODO document
- *
- * If this method returns false, something was submitted that we did not
- * expect; this is probably due to the client being out-of-sync and sending
- * variable changes for non-existing pids
- *
- * @return true if successful, false if there was an inconsistency
- */
- private boolean handleVariables(WrappedRequest request,
- WrappedResponse response, Callback callback,
- Application application2, Root root) throws IOException,
- InvalidUIDLSecurityKeyException {
- boolean success = true;
-
- String changes = getRequestPayload(request);
- if (changes != null) {
-
- // Manage bursts one by one
- final String[] bursts = changes.split(String
- .valueOf(VAR_BURST_SEPARATOR));
-
- // Security: double cookie submission pattern unless disabled by
- // property
- if (isXSRFEnabled(application2)) {
- if (bursts.length == 1 && "init".equals(bursts[0])) {
- // init request; don't handle any variables, key sent in
- // response.
- request.setAttribute(WRITE_SECURITY_TOKEN_FLAG, true);
- return true;
- } else {
- // ApplicationServlet has stored the security token in the
- // session; check that it matched the one sent in the UIDL
- String sessId = (String) request
- .getSessionAttribute(ApplicationConnection.UIDL_SECURITY_TOKEN_ID);
-
- if (sessId == null || !sessId.equals(bursts[0])) {
- throw new InvalidUIDLSecurityKeyException(
- "Security key mismatch");
- }
- }
-
- }
-
- for (int bi = 1; bi < bursts.length; bi++) {
- final String burst = bursts[bi];
- success = handleVariableBurst(request, application2, success,
- burst);
-
- // In case that there were multiple bursts, we know that this is
- // a special synchronous case for closing window. Thus we are
- // not interested in sending any UIDL changes back to client.
- // Still we must clear component tree between bursts to ensure
- // that no removed components are updated. The painting after
- // the last burst is handled normally by the calling method.
- if (bi < bursts.length - 1) {
-
- // We will be discarding all changes
- final PrintWriter outWriter = new PrintWriter(
- new CharArrayWriter());
-
- paintAfterVariableChanges(request, response, callback,
- true, outWriter, root, false);
-
- }
-
- }
- }
- /*
- * Note that we ignore inconsistencies while handling unload request.
- * The client can't remove invalid variable changes from the burst, and
- * we don't have the required logic implemented on the server side. E.g.
- * a component is removed in a previous burst.
- */
- return success;
- }
-
- public boolean handleVariableBurst(Object source, Application app,
- boolean success, final String burst) {
- // extract variables to two dim string array
- final String[] tmp = burst.split(String.valueOf(VAR_RECORD_SEPARATOR));
- final String[][] variableRecords = new String[tmp.length][4];
- for (int i = 0; i < tmp.length; i++) {
- variableRecords[i] = tmp[i].split(String
- .valueOf(VAR_FIELD_SEPARATOR));
- }
-
- for (int i = 0; i < variableRecords.length; i++) {
- String[] variable = variableRecords[i];
- String[] nextVariable = null;
- if (i + 1 < variableRecords.length) {
- nextVariable = variableRecords[i + 1];
- }
- final VariableOwner owner = getVariableOwner(variable[VAR_PID]);
- if (owner != null && owner.isEnabled()) {
- Map<String, Object> m;
- if (nextVariable != null
- && variable[VAR_PID].equals(nextVariable[VAR_PID])) {
- // we have more than one value changes in row for
- // one variable owner, collect them in HashMap
- m = new HashMap<String, Object>();
- m.put(variable[VAR_NAME],
- convertVariableValue(variable[VAR_TYPE].charAt(0),
- variable[VAR_VALUE]));
- } else {
- // use optimized single value map
- m = Collections.singletonMap(
- variable[VAR_NAME],
- convertVariableValue(variable[VAR_TYPE].charAt(0),
- variable[VAR_VALUE]));
- }
-
- // collect following variable changes for this owner
- while (nextVariable != null
- && variable[VAR_PID].equals(nextVariable[VAR_PID])) {
- i++;
- variable = nextVariable;
- if (i + 1 < variableRecords.length) {
- nextVariable = variableRecords[i + 1];
- } else {
- nextVariable = null;
- }
- m.put(variable[VAR_NAME],
- convertVariableValue(variable[VAR_TYPE].charAt(0),
- variable[VAR_VALUE]));
- }
- try {
- changeVariables(source, owner, m);
- } catch (Exception e) {
- if (owner instanceof Component) {
- handleChangeVariablesError(app, (Component) owner, e, m);
- } else {
- // TODO DragDropService error handling
- throw new RuntimeException(e);
- }
- }
- } else {
-
- // Handle special case where window-close is called
- // after the window has been removed from the
- // application or the application has closed
- if ("close".equals(variable[VAR_NAME])
- && "true".equals(variable[VAR_VALUE])) {
- // Silently ignore this
- continue;
- }
-
- // Ignore variable change
- String msg = "Warning: Ignoring variable change for ";
- if (owner != null) {
- msg += "disabled component " + owner.getClass();
- String caption = ((Component) owner).getCaption();
- if (caption != null) {
- msg += ", caption=" + caption;
- }
- } else {
- msg += "non-existent component, VAR_PID="
- + variable[VAR_PID];
- success = false;
- }
- logger.warning(msg);
- continue;
- }
- }
- return success;
- }
-
- protected void changeVariables(Object source, final VariableOwner owner,
- Map<String, Object> m) {
- owner.changeVariables(source, m);
- }
-
- protected VariableOwner getVariableOwner(String string) {
- VariableOwner owner = (VariableOwner) idPaintableMap.get(string);
- if (owner == null && string.startsWith("DD")) {
- return getDragAndDropService();
- }
- return owner;
- }
-
- private VariableOwner getDragAndDropService() {
- if (dragAndDropService == null) {
- dragAndDropService = new DragAndDropService(this);
- }
- return dragAndDropService;
- }
-
- /**
- * Reads the request data from the Request and returns it converted to an
- * UTF-8 string.
- *
- * @param request
- * @return
- * @throws IOException
- */
- protected String getRequestPayload(WrappedRequest request)
- throws IOException {
-
- int requestLength = request.getContentLength();
- if (requestLength == 0) {
- return null;
- }
-
- ByteArrayOutputStream bout = requestLength <= 0 ? new ByteArrayOutputStream()
- : new ByteArrayOutputStream(requestLength);
-
- InputStream inputStream = request.getInputStream();
- byte[] buffer = new byte[MAX_BUFFER_SIZE];
-
- while (true) {
- int read = inputStream.read(buffer);
- if (read == -1) {
- break;
- }
- bout.write(buffer, 0, read);
- }
- String result = new String(bout.toByteArray(), "utf-8");
-
- return result;
- }
-
- public class ErrorHandlerErrorEvent implements ErrorEvent, Serializable {
- private final Throwable throwable;
-
- public ErrorHandlerErrorEvent(Throwable throwable) {
- this.throwable = throwable;
- }
-
- public Throwable getThrowable() {
- return throwable;
- }
-
- }
-
- /**
- * Handles an error (exception) that occurred when processing variable
- * changes from the client or a failure of a file upload.
- *
- * For {@link AbstractField} components,
- * {@link AbstractField#handleError(com.vaadin.ui.AbstractComponent.ComponentErrorEvent)}
- * is called. In all other cases (or if the field does not handle the
- * error), {@link ErrorListener#terminalError(ErrorEvent)} for the
- * application error handler is called.
- *
- * @param application
- * @param owner
- * component that the error concerns
- * @param e
- * exception that occurred
- * @param m
- * map from variable names to values
- */
- private void handleChangeVariablesError(Application application,
- Component owner, Exception e, Map<String, Object> m) {
- boolean handled = false;
- ChangeVariablesErrorEvent errorEvent = new ChangeVariablesErrorEvent(
- owner, e, m);
-
- if (owner instanceof AbstractField) {
- try {
- handled = ((AbstractField<?>) owner).handleError(errorEvent);
- } catch (Exception handlerException) {
- /*
- * If there is an error in the component error handler we pass
- * the that error to the application error handler and continue
- * processing the actual error
- */
- application.getErrorHandler().terminalError(
- new ErrorHandlerErrorEvent(handlerException));
- handled = false;
- }
- }
-
- if (!handled) {
- application.getErrorHandler().terminalError(errorEvent);
- }
-
- }
-
- private Object convertVariableValue(char variableType, String strValue) {
- Object val = null;
- switch (variableType) {
- case VTYPE_ARRAY:
- val = convertArray(strValue);
- break;
- case VTYPE_MAP:
- val = convertMap(strValue);
- break;
- case VTYPE_STRINGARRAY:
- val = convertStringArray(strValue);
- break;
- case VTYPE_STRING:
- // decode encoded separators
- val = decodeVariableValue(strValue);
- break;
- case VTYPE_INTEGER:
- val = Integer.valueOf(strValue);
- break;
- case VTYPE_LONG:
- val = Long.valueOf(strValue);
- break;
- case VTYPE_FLOAT:
- val = Float.valueOf(strValue);
- break;
- case VTYPE_DOUBLE:
- val = Double.valueOf(strValue);
- break;
- case VTYPE_BOOLEAN:
- val = Boolean.valueOf(strValue);
- break;
- case VTYPE_PAINTABLE:
- val = idPaintableMap.get(strValue);
- break;
- }
-
- return val;
- }
-
- private Object convertMap(String strValue) {
- String[] parts = strValue
- .split(String.valueOf(VAR_ARRAYITEM_SEPARATOR));
- HashMap<String, Object> map = new HashMap<String, Object>();
- for (int i = 0; i < parts.length; i += 2) {
- String key = parts[i];
- if (key.length() > 0) {
- char variabletype = key.charAt(0);
- // decode encoded separators
- String decodedValue = decodeVariableValue(parts[i + 1]);
- String decodedKey = decodeVariableValue(key.substring(1));
- Object value = convertVariableValue(variabletype, decodedValue);
- map.put(decodedKey, value);
- }
- }
- return map;
- }
-
- private String[] convertStringArray(String strValue) {
- // need to return delimiters and filter them out; otherwise empty
- // strings are lost
- // an extra empty delimiter at the end is automatically eliminated
- final String arrayItemSeparator = String
- .valueOf(VAR_ARRAYITEM_SEPARATOR);
- StringTokenizer tokenizer = new StringTokenizer(strValue,
- arrayItemSeparator, true);
- List<String> tokens = new ArrayList<String>();
- String prevToken = arrayItemSeparator;
- while (tokenizer.hasMoreTokens()) {
- String token = tokenizer.nextToken();
- if (!arrayItemSeparator.equals(token)) {
- // decode encoded separators
- tokens.add(decodeVariableValue(token));
- } else if (arrayItemSeparator.equals(prevToken)) {
- tokens.add("");
- }
- prevToken = token;
- }
- return tokens.toArray(new String[tokens.size()]);
- }
-
- private Object convertArray(String strValue) {
- String[] val = strValue.split(String.valueOf(VAR_ARRAYITEM_SEPARATOR));
- if (val.length == 0 || (val.length == 1 && val[0].length() == 0)) {
- return new Object[0];
- }
- Object[] values = new Object[val.length];
- for (int i = 0; i < values.length; i++) {
- String string = val[i];
- // first char of string is type
- char variableType = string.charAt(0);
- values[i] = convertVariableValue(variableType, string.substring(1));
- }
- return values;
- }
-
- /**
- * Decode encoded burst, record, field and array item separator characters
- * in a variable value String received from the client. This protects from
- * separator injection attacks.
- *
- * @param encodedValue
- * to decode
- * @return decoded value
- */
- protected String decodeVariableValue(String encodedValue) {
- final StringBuilder result = new StringBuilder();
- final StringCharacterIterator iterator = new StringCharacterIterator(
- encodedValue);
- char character = iterator.current();
- while (character != CharacterIterator.DONE) {
- if (VAR_ESCAPE_CHARACTER == character) {
- character = iterator.next();
- switch (character) {
- case VAR_ESCAPE_CHARACTER + 0x30:
- // escaped escape character
- result.append(VAR_ESCAPE_CHARACTER);
- break;
- case VAR_BURST_SEPARATOR + 0x30:
- case VAR_RECORD_SEPARATOR + 0x30:
- case VAR_FIELD_SEPARATOR + 0x30:
- case VAR_ARRAYITEM_SEPARATOR + 0x30:
- // +0x30 makes these letters for easier reading
- result.append((char) (character - 0x30));
- break;
- case CharacterIterator.DONE:
- // error
- throw new RuntimeException(
- "Communication error: Unexpected end of message");
- default:
- // other escaped character - probably a client-server
- // version mismatch
- throw new RuntimeException(
- "Invalid escaped character from the client - check that the widgetset and server versions match");
- }
- } else {
- // not a special character - add it to the result as is
- result.append(character);
- }
- character = iterator.next();
- }
- return result.toString();
- }
-
- /**
- * Prints the queued (pending) locale definitions to a {@link PrintWriter}
- * in a (UIDL) format that can be sent to the client and used there in
- * formatting dates, times etc.
- *
- * @param outWriter
- */
- private void printLocaleDeclarations(PrintWriter outWriter) {
- /*
- * ----------------------------- Sending Locale sensitive date
- * -----------------------------
- */
-
- // Send locale informations to client
- outWriter.print(", \"locales\":[");
- for (; pendingLocalesIndex < locales.size(); pendingLocalesIndex++) {
-
- final Locale l = generateLocale(locales.get(pendingLocalesIndex));
- // Locale name
- outWriter.print("{\"name\":\"" + l.toString() + "\",");
-
- /*
- * Month names (both short and full)
- */
- final DateFormatSymbols dfs = new DateFormatSymbols(l);
- final String[] short_months = dfs.getShortMonths();
- final String[] months = dfs.getMonths();
- outWriter.print("\"smn\":[\""
- + // ShortMonthNames
- short_months[0] + "\",\"" + short_months[1] + "\",\""
- + short_months[2] + "\",\"" + short_months[3] + "\",\""
- + short_months[4] + "\",\"" + short_months[5] + "\",\""
- + short_months[6] + "\",\"" + short_months[7] + "\",\""
- + short_months[8] + "\",\"" + short_months[9] + "\",\""
- + short_months[10] + "\",\"" + short_months[11] + "\""
- + "],");
- outWriter.print("\"mn\":[\""
- + // MonthNames
- months[0] + "\",\"" + months[1] + "\",\"" + months[2]
- + "\",\"" + months[3] + "\",\"" + months[4] + "\",\""
- + months[5] + "\",\"" + months[6] + "\",\"" + months[7]
- + "\",\"" + months[8] + "\",\"" + months[9] + "\",\""
- + months[10] + "\",\"" + months[11] + "\"" + "],");
-
- /*
- * Weekday names (both short and full)
- */
- final String[] short_days = dfs.getShortWeekdays();
- final String[] days = dfs.getWeekdays();
- outWriter.print("\"sdn\":[\""
- + // ShortDayNames
- short_days[1] + "\",\"" + short_days[2] + "\",\""
- + short_days[3] + "\",\"" + short_days[4] + "\",\""
- + short_days[5] + "\",\"" + short_days[6] + "\",\""
- + short_days[7] + "\"" + "],");
- outWriter.print("\"dn\":[\""
- + // DayNames
- days[1] + "\",\"" + days[2] + "\",\"" + days[3] + "\",\""
- + days[4] + "\",\"" + days[5] + "\",\"" + days[6] + "\",\""
- + days[7] + "\"" + "],");
-
- /*
- * First day of week (0 = sunday, 1 = monday)
- */
- final Calendar cal = new GregorianCalendar(l);
- outWriter.print("\"fdow\":" + (cal.getFirstDayOfWeek() - 1) + ",");
-
- /*
- * Date formatting (MM/DD/YYYY etc.)
- */
-
- DateFormat dateFormat = DateFormat.getDateTimeInstance(
- DateFormat.SHORT, DateFormat.SHORT, l);
- if (!(dateFormat instanceof SimpleDateFormat)) {
- logger.warning("Unable to get default date pattern for locale "
- + l.toString());
- dateFormat = new SimpleDateFormat();
- }
- final String df = ((SimpleDateFormat) dateFormat).toPattern();
-
- int timeStart = df.indexOf("H");
- if (timeStart < 0) {
- timeStart = df.indexOf("h");
- }
- final int ampm_first = df.indexOf("a");
- // E.g. in Korean locale AM/PM is before h:mm
- // TODO should take that into consideration on client-side as well,
- // now always h:mm a
- if (ampm_first > 0 && ampm_first < timeStart) {
- timeStart = ampm_first;
- }
- // Hebrew locale has time before the date
- final boolean timeFirst = timeStart == 0;
- String dateformat;
- if (timeFirst) {
- int dateStart = df.indexOf(' ');
- if (ampm_first > dateStart) {
- dateStart = df.indexOf(' ', ampm_first);
- }
- dateformat = df.substring(dateStart + 1);
- } else {
- dateformat = df.substring(0, timeStart - 1);
- }
-
- outWriter.print("\"df\":\"" + dateformat.trim() + "\",");
-
- /*
- * Time formatting (24 or 12 hour clock and AM/PM suffixes)
- */
- final String timeformat = df.substring(timeStart, df.length());
- /*
- * Doesn't return second or milliseconds.
- *
- * We use timeformat to determine 12/24-hour clock
- */
- final boolean twelve_hour_clock = timeformat.indexOf("a") > -1;
- // TODO there are other possibilities as well, like 'h' in french
- // (ignore them, too complicated)
- final String hour_min_delimiter = timeformat.indexOf(".") > -1 ? "."
- : ":";
- // outWriter.print("\"tf\":\"" + timeformat + "\",");
- outWriter.print("\"thc\":" + twelve_hour_clock + ",");
- outWriter.print("\"hmd\":\"" + hour_min_delimiter + "\"");
- if (twelve_hour_clock) {
- final String[] ampm = dfs.getAmPmStrings();
- outWriter.print(",\"ampm\":[\"" + ampm[0] + "\",\"" + ampm[1]
- + "\"]");
- }
- outWriter.print("}");
- if (pendingLocalesIndex < locales.size() - 1) {
- outWriter.print(",");
- }
- }
- outWriter.print("]"); // Close locales
- }
-
- /**
- * Ends the Application.
- *
- * The browser is redirected to the Application logout URL set with
- * {@link Application#setLogoutURL(String)}, or to the application URL if no
- * logout URL is given.
- *
- * @param request
- * the request instance.
- * @param response
- * the response to write to.
- * @param application
- * the Application to end.
- * @throws IOException
- * if the writing failed due to input/output error.
- */
- private void endApplication(WrappedRequest request,
- WrappedResponse response, Application application)
- throws IOException {
-
- String logoutUrl = application.getLogoutURL();
- if (logoutUrl == null) {
- logoutUrl = application.getURL().toString();
- }
- // clients JS app is still running, send a special json file to tell
- // client that application has quit and where to point browser now
- // Set the response type
- final OutputStream out = response.getOutputStream();
- final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
- new OutputStreamWriter(out, "UTF-8")));
- openJsonMessage(outWriter, response);
- outWriter.print("\"redirect\":{");
- outWriter.write("\"url\":\"" + logoutUrl + "\"}");
- closeJsonMessage(outWriter);
- outWriter.flush();
- outWriter.close();
- out.flush();
- }
-
- protected void closeJsonMessage(PrintWriter outWriter) {
- outWriter.print("}]");
- }
-
- /**
- * Writes the opening of JSON message to be sent to client.
- *
- * @param outWriter
- * @param response
- */
- protected void openJsonMessage(PrintWriter outWriter,
- WrappedResponse response) {
- // Sets the response type
- response.setContentType("application/json; charset=UTF-8");
- // some dirt to prevent cross site scripting
- outWriter.print("for(;;);[{");
- }
-
- /**
- * Gets the Paintable Id. If Paintable has debug id set it will be used
- * prefixed with "PID_S". Otherwise a sequenced ID is created.
- *
- * @param paintable
- * @return the paintable Id.
- */
- public String getPaintableId(Paintable paintable) {
-
- String id = paintableIdMap.get(paintable);
- if (id == null) {
- // use testing identifier as id if set
- id = paintable.getDebugId();
- if (id == null) {
- id = "PID" + Integer.toString(idSequence++);
- } else {
- id = "PID_S" + id;
- }
- Paintable old = idPaintableMap.put(id, paintable);
- if (old != null && old != paintable) {
- /*
- * Two paintables have the same id. We still make sure the old
- * one is a component which is still attached to the
- * application. This is just a precaution and should not be
- * absolutely necessary.
- */
-
- if (old instanceof Component
- && ((Component) old).getApplication() != null) {
- throw new IllegalStateException("Two paintables ("
- + paintable.getClass().getSimpleName() + ","
- + old.getClass().getSimpleName()
- + ") have been assigned the same id: "
- + paintable.getDebugId());
- }
- }
- paintableIdMap.put(paintable, id);
- }
-
- return id;
- }
-
- public boolean hasPaintableId(Paintable paintable) {
- return paintableIdMap.containsKey(paintable);
- }
-
- /**
- * Returns dirty components which are in given window. Components in an
- * invisible subtrees are omitted.
- *
- * @param w
- * root window for which dirty components is to be fetched
- * @return
- */
- private ArrayList<Paintable> getDirtyVisibleComponents(Root r) {
- final ArrayList<Paintable> resultset = new ArrayList<Paintable>(
- dirtyPaintables);
-
- // The following algorithm removes any components that would be painted
- // as a direct descendant of other components from the dirty components
- // list. The result is that each component should be painted exactly
- // once and any unmodified components will be painted as "cached=true".
-
- for (final Iterator<Paintable> i = dirtyPaintables.iterator(); i
- .hasNext();) {
- final Paintable p = i.next();
- if (p instanceof Component) {
- final Component component = (Component) p;
- if (component.getApplication() == null) {
- // component is detached after requestRepaint is called
- resultset.remove(p);
- i.remove();
- } else {
- Root componentsRoot = component.getRoot();
- if (componentsRoot == null) {
- // This should not happen unless somebody has overriden
- // getApplication or getWindow in an illegal way.
- throw new IllegalStateException(
- "component.getWindow() returned null for a component attached to the application");
- }
- // if (componentsRoot.getParent() != null) {
- // // this is a subwindow
- // componentsRoot = componentsRoot.getParent();
- // }
- if (componentsRoot != r) {
- resultset.remove(p);
- } else if (component.getParent() != null
- && !component.getParent().isVisible()) {
- /*
- * Do not return components in an invisible subtree.
- *
- * Components that are invisible in visible subree, must
- * be rendered (to let client know that they need to be
- * hidden).
- */
- resultset.remove(p);
- }
- }
- }
- }
-
- return resultset;
- }
-
- /**
- * @see com.vaadin.terminal.Paintable.RepaintRequestListener#repaintRequested(com.vaadin.terminal.Paintable.RepaintRequestEvent)
- */
- public void repaintRequested(RepaintRequestEvent event) {
- final Paintable p = event.getPaintable();
- if (!dirtyPaintables.contains(p)) {
- dirtyPaintables.add(p);
- }
- }
-
- /**
- * Internally mark a {@link Paintable} as painted and start collecting new
- * repaint requests for it.
- *
- * @param paintable
- */
- private void paintablePainted(Paintable paintable) {
- dirtyPaintables.remove(paintable);
- paintable.requestRepaintRequests();
- }
-
- /**
- * Queues a locale to be sent to the client (browser) for date and time
- * entry etc. All locale specific information is derived from server-side
- * {@link Locale} instances and sent to the client when needed, eliminating
- * the need to use the {@link Locale} class and all the framework behind it
- * on the client.
- *
- * @see Locale#toString()
- *
- * @param value
- */
- public void requireLocale(String value) {
- if (locales == null) {
- locales = new ArrayList<String>();
- locales.add(application.getLocale().toString());
- pendingLocalesIndex = 0;
- }
- if (!locales.contains(value)) {
- locales.add(value);
- }
- }
-
- /**
- * Constructs a {@link Locale} instance to be sent to the client based on a
- * short locale description string.
- *
- * @see #requireLocale(String)
- *
- * @param value
- * @return
- */
- private Locale generateLocale(String value) {
- final String[] temp = value.split("_");
- if (temp.length == 1) {
- return new Locale(temp[0]);
- } else if (temp.length == 2) {
- return new Locale(temp[0], temp[1]);
- } else {
- return new Locale(temp[0], temp[1], temp[2]);
- }
- }
-
- /**
- * Helper method to test if a component contains another
- *
- * @param parent
- * @param child
- */
- private static boolean isChildOf(Component parent, Component child) {
- Component p = child.getParent();
- while (p != null) {
- if (parent == p) {
- return true;
- }
- p = p.getParent();
- }
- return false;
- }
-
- protected class InvalidUIDLSecurityKeyException extends
- GeneralSecurityException {
-
- InvalidUIDLSecurityKeyException(String message) {
- super(message);
- }
-
- }
-
- private final HashMap<Class<? extends Paintable>, Integer> typeToKey = new HashMap<Class<? extends Paintable>, Integer>();
- private int nextTypeKey = 0;
-
- private BootstrapHandler bootstrapHandler;
-
- String getTagForType(Class<? extends Paintable> class1) {
- Integer object = typeToKey.get(class1);
- if (object == null) {
- object = nextTypeKey++;
- typeToKey.put(class1, object);
- }
- return object.toString();
- }
-
- /**
- * Helper class for terminal to keep track of data that client is expected
- * to know.
- *
- * TODO make customlayout templates (from theme) to be cached here.
- */
- class OpenWindowCache implements Serializable {
-
- private final Set<Object> res = new HashSet<Object>();
-
- /**
- *
- * @param paintable
- * @return true if the given class was added to cache
- */
- boolean cache(Object object) {
- return res.add(object);
- }
-
- public void clear() {
- res.clear();
- }
-
- }
-
- abstract String getStreamVariableTargetUrl(VariableOwner owner,
- String name, StreamVariable value);
-
- abstract protected void cleanStreamVariable(VariableOwner owner, String name);
-
- /**
- * Gets the bootstrap handler that should be used for generating the pages
- * bootstrapping applications for this communication manager.
- *
- * @return the bootstrap handler to use
- */
- private BootstrapHandler getBootstrapHandler() {
- if (bootstrapHandler == null) {
- bootstrapHandler = createBootstrapHandler();
- }
-
- return bootstrapHandler;
- }
-
- protected abstract BootstrapHandler createBootstrapHandler();
-
- protected boolean handleApplicationRequest(WrappedRequest request,
- WrappedResponse response) throws IOException {
- return application.handleRequest(request, response);
- }
-
- public void handleBrowserDetailsRequest(WrappedRequest request,
- WrappedResponse response, Application application)
- throws IOException {
-
- // if we do not yet have a currentRoot, it should be initialized
- // shortly, and we should send the initial UIDL
- boolean sendUIDL = Root.getCurrentRoot() == null;
-
- try {
- CombinedRequest combinedRequest = new CombinedRequest(request);
-
- Root root = application.getRootForRequest(combinedRequest);
- response.setContentType("application/json; charset=UTF-8");
-
- // Use the same logic as for determined roots
- BootstrapHandler bootstrapHandler = getBootstrapHandler();
- BootstrapContext context = bootstrapHandler.createContext(
- combinedRequest, response, application, root.getRootId());
-
- String widgetset = context.getWidgetsetName();
- String theme = context.getThemeName();
- String themeUri = bootstrapHandler.getThemeUri(context, theme);
-
- // TODO These are not required if it was only the init of the root
- // that was delayed
- JSONObject params = new JSONObject();
- params.put("widgetset", widgetset);
- params.put("themeUri", themeUri);
- // Root id might have changed based on e.g. window.name
- params.put(ApplicationConnection.ROOT_ID_PARAMETER,
- root.getRootId());
- if (sendUIDL) {
- String initialUIDL = getInitialUIDL(combinedRequest, root);
- params.put("uidl", initialUIDL);
- }
- response.getWriter().write(params.toString());
- } catch (RootRequiresMoreInformationException e) {
- // Requiring more information at this point is not allowed
- // TODO handle in a better way
- throw new RuntimeException(e);
- } catch (JSONException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
-
- /**
- * Generates the initial UIDL message that can e.g. be included in a html
- * page to avoid a separate round trip just for getting the UIDL.
- *
- * @param request
- * the request that caused the initialization
- * @param root
- * the root for which the UIDL should be generated
- * @return a string with the initial UIDL message
- * @throws PaintException
- * if an exception occurs while painting
- */
- protected String getInitialUIDL(WrappedRequest request, Root root)
- throws PaintException {
- // TODO maybe unify writeUidlResponCe()?
- makeAllPaintablesDirty(root);
- StringWriter sWriter = new StringWriter();
- PrintWriter pWriter = new PrintWriter(sWriter);
- pWriter.print("{");
- if (isXSRFEnabled(root.getApplication())) {
- pWriter.print(getSecurityKeyUIDL(request));
- }
- writeUidlResponce(true, pWriter, root, false);
- pWriter.print("}");
- String initialUIDL = sWriter.toString();
- return initialUIDL;
- }
-
- /**
- * Stream that extracts content from another stream until the boundary
- * string is encountered.
- *
- * Public only for unit tests, should be considered private for all other
- * purposes.
- */
- public static class SimpleMultiPartInputStream extends InputStream {
-
- /**
- * Counter of how many characters have been matched to boundary string
- * from the stream
- */
- int matchedCount = -1;
-
- /**
- * Used as pointer when returning bytes after partly matched boundary
- * string.
- */
- int curBoundaryIndex = 0;
- /**
- * The byte found after a "promising start for boundary"
- */
- private int bufferedByte = -1;
- private boolean atTheEnd = false;
-
- private final char[] boundary;
-
- private final InputStream realInputStream;
-
- public SimpleMultiPartInputStream(InputStream realInputStream,
- String boundaryString) {
- boundary = (CRLF + DASHDASH + boundaryString).toCharArray();
- this.realInputStream = realInputStream;
- }
-
- @Override
- public int read() throws IOException {
- if (atTheEnd) {
- // End boundary reached, nothing more to read
- return -1;
- } else if (bufferedByte >= 0) {
- /* Purge partially matched boundary if there was such */
- return getBuffered();
- } else if (matchedCount != -1) {
- /*
- * Special case where last "failed" matching ended with first
- * character from boundary string
- */
- return matchForBoundary();
- } else {
- int fromActualStream = realInputStream.read();
- if (fromActualStream == -1) {
- // unexpected end of stream
- throw new IOException(
- "The multipart stream ended unexpectedly");
- }
- if (boundary[0] == fromActualStream) {
- /*
- * If matches the first character in boundary string, start
- * checking if the boundary is fetched.
- */
- return matchForBoundary();
- }
- return fromActualStream;
- }
- }
-
- /**
- * Reads the input to expect a boundary string. Expects that the first
- * character has already been matched.
- *
- * @return -1 if the boundary was matched, else returns the first byte
- * from boundary
- * @throws IOException
- */
- private int matchForBoundary() throws IOException {
- matchedCount = 0;
- /*
- * Going to "buffered mode". Read until full boundary match or a
- * different character.
- */
- while (true) {
- matchedCount++;
- if (matchedCount == boundary.length) {
- /*
- * The whole boundary matched so we have reached the end of
- * file
- */
- atTheEnd = true;
- return -1;
- }
- int fromActualStream = realInputStream.read();
- if (fromActualStream != boundary[matchedCount]) {
- /*
- * Did not find full boundary, cache the mismatching byte
- * and start returning the partially matched boundary.
- */
- bufferedByte = fromActualStream;
- return getBuffered();
- }
- }
- }
-
- /**
- * Returns the partly matched boundary string and the byte following
- * that.
- *
- * @return
- * @throws IOException
- */
- private int getBuffered() throws IOException {
- int b;
- if (matchedCount == 0) {
- // The boundary has been returned, return the buffered byte.
- b = bufferedByte;
- bufferedByte = -1;
- matchedCount = -1;
- } else {
- b = boundary[curBoundaryIndex++];
- if (curBoundaryIndex == matchedCount) {
- // The full boundary has been returned, remaining is the
- // char that did not match the boundary.
-
- curBoundaryIndex = 0;
- if (bufferedByte != boundary[0]) {
- /*
- * next call for getBuffered will return the
- * bufferedByte that came after the partial boundary
- * match
- */
- matchedCount = 0;
- } else {
- /*
- * Special case where buffered byte again matches the
- * boundaryString. This could be the start of the real
- * end boundary.
- */
- matchedCount = 0;
- bufferedByte = -1;
- }
- }
- }
- if (b == -1) {
- throw new IOException("The multipart stream ended unexpectedly");
- }
- return b;
- }
- }
- }
|