001 /*
002 Copyright (c) 1996-2012, Damon Hart-Davis
003 All rights reserved.
004
005 Redistribution and use in source and binary forms, with or without
006 modification, are permitted provided that the following conditions are
007 met:
008
009 * Redistributions of source code must retain the above copyright
010 notice, this list of conditions and the following disclaimer.
011
012 * Redistributions in binary form must reproduce the above copyright
013 notice, this list of conditions and the following disclaimer in the
014 documentation and/or other materials provided with the
015 distribution.
016
017 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
018 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
019 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
020 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
021 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
022 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
023 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
024 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
025 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
026 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
027 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028 */
029
030 package org.hd.d.pg2k.webSvr.threeD;
031
032 import java.io.DataInputStream;
033 import java.io.IOException;
034 import java.io.InputStream;
035 import java.net.HttpURLConnection;
036 import java.net.URL;
037
038 import org.hd.d.pg2k.svrCore.CoreConsts;
039 import org.hd.d.pg2k.svrCore.ExhibitName;
040 import org.hd.d.pg2k.svrCore.Name;
041 import org.hd.d.pg2k.svrCore.Rnd;
042 import org.hd.d.pg2k.svrCore.MIME.ExhibitMIME;
043
044
045 /**3D Walkthrough utilities.
046 * Shared by the JWS code and the supporting server-side code.
047 */
048 final class Utils
049 {
050 /**Prevent construction of an instance. */
051 private Utils() { }
052
053
054 /**Returns true for exhibit types supported in the walkthrough, false otherwise. */
055 public static boolean supportedTypeFor3DWT(final ExhibitMIME.ExhibitTypeParameters exhibitType)
056 {
057 switch(exhibitType.type)
058 {
059 case ExhibitMIME.ET_JPEG:
060 case ExhibitMIME.ET_GIF:
061 case ExhibitMIME.ET_PNG:
062 return(true); // These types are potentially OK...
063
064 default:
065 return(false);
066 }
067 }
068
069
070
071 /**Expected mount point of this servlet; starts with "/". */
072 public static final String SERVLET_MOUNT_POINT = "/_3D/data";
073
074 /**HTTP parameter used to select the operation type; URL safe. */
075 public static final String HP_OPCODE = "op";
076 /**HTTP parameter used to indicate the exhibit ordinal(s); URL safe. */
077 public static final String HP_ORD = "n";
078 /**Operation type to request basic metadata; URL safe. */
079 public static final String OP_MD = "md";
080 // /**Operation type to request exhibit name lookup by ordinal; URL safe. */
081 // public static final String OP_LU = "lu";
082 /**Operation type to request multiple exhibit name lookups by ordinal; URL safe. */
083 public static final String OP_LUS = "lus";
084
085 /**"File" portion of meta-data request. */
086 private static final String MDREQ = SERVLET_MOUNT_POINT + '?' + HP_OPCODE + '=' + OP_MD;
087
088 /**Basic connect timeout for HTTP/RPCs for reasonable interactivity, in ms; strictly positive.
089 * Generally HTTP/TCP connects very quickly if it is going to connect at all
090 * and anything longer usually implies a lost handshake packet
091 * or a dead network connection, so waiting a longer time rarely helps.
092 * <p>
093 * The random factor helps to prevent different clients from colliding.
094 */
095 static final int RPC_HTTP_CONNECT_TIMEOUT_MS = 2011 + Rnd.fastRnd.nextInt(511);
096
097 /**Basic read timeout for HTTP/RPCs for reasonable interactivity, in ms; strictly positive.
098 * Once we have connected then the network is probably OK,
099 * so it is worth waiting a while in case the server needs time to
100 * compute the results; so the read timeout should usually be a little more
101 * than the connect timeout.
102 * <p>
103 * The random factor helps to prevent separate clients from colliding.
104 */
105 static final int RPC_HTTP_READ_TIMEOUT_MS = 2*RPC_HTTP_CONNECT_TIMEOUT_MS + 1003 + Rnd.fastRnd.nextInt(1003);
106
107 /**Client-side call to fetch metadata from the server; never null.
108 *
109 * @param hostAndPort server host with optional port,
110 * or null to use the default/main server name
111 *
112 * @throws java.io.IOException in case of error
113 */
114 static final LightweightMetaDataFetchInterface.GalleryBasicMetaData getMetaData(URL hostAndPort)
115 throws IOException
116 {
117 if(hostAndPort == null) { hostAndPort = new URL("http", CoreConsts.MAIN_DATA_HOST, "/"); }
118
119 final HttpURLConnection urlConnection = _setUpConnection(hostAndPort, MDREQ);
120 final int contentLength = urlConnection.getContentLength();
121 if((contentLength < 5) || (contentLength > 128))
122 { throw new IOException("corrupt response: Content-Length " + contentLength); }
123
124 // Read and parse response.
125 final InputStream inputStream = urlConnection.getInputStream();
126 try
127 {
128 final DataInputStream dis = new DataInputStream(inputStream);
129 final byte buf[] = new byte[contentLength];
130 dis.readFully(buf);
131 final StringBuilder sbi = new StringBuilder(contentLength);
132 for(int i = 0; i < contentLength; ++i)
133 { sbi.append((char) buf[i]); }
134 try { return(LightweightMetaDataFetchInterface.GalleryBasicMetaData.fromWireString(sbi.toString())); }
135 catch(final Exception e) { throw new IOException("unparsable response"); }
136 }
137 finally
138 { inputStream.close(); /* Free resources. */ }
139 }
140
141 /**Set up and open HTTP connection to server for RPC; never null. */
142 private static HttpURLConnection _setUpConnection(final URL hostAndPort,
143 final String rrURL)
144 throws IOException
145 {
146 final HttpURLConnection urlConnection =
147 (HttpURLConnection) (new URL(hostAndPort.getProtocol(), hostAndPort.getHost(), hostAndPort.getPort(), rrURL)).openConnection();
148 urlConnection.setAllowUserInteraction(false);
149 urlConnection.setConnectTimeout(RPC_HTTP_CONNECT_TIMEOUT_MS);
150 urlConnection.setReadTimeout(RPC_HTTP_READ_TIMEOUT_MS);
151 urlConnection.setRequestProperty("Referer", "http://" + CoreConsts.MAIN_DATA_HOST + SERVLET_MOUNT_POINT);
152 urlConnection.setUseCaches(false);
153 final int responseCode = urlConnection.getResponseCode();
154 if(200 != responseCode)
155 { throw new IOException("unexpected response code " + responseCode + " from " + urlConnection.getURL()); }
156 return(urlConnection);
157 }
158
159 // /**Client-side call to get exhibit name (by index) from the server; never null.
160 // *
161 // * @param hostAndPort server host with optional port,
162 // * or null to use the default/main server name
163 // *
164 // * @throws java.io.IOException in case of error
165 // */
166 // static final String getExhibitName(URL hostAndPort, final int ordinal)
167 // throws IOException
168 // {
169 // if(ordinal < 0)
170 // { throw new IllegalArgumentException(); }
171 //
172 // if(hostAndPort == null) { hostAndPort = new URL("http", CoreConsts.MAIN_DATA_HOST, "/"); }
173 //
174 // final HttpURLConnection urlConnection = _setUpConnection(hostAndPort, SERVLET_MOUNT_POINT + '?' + HP_OPCODE + '=' + OP_LU + '&' + HP_ORD + '=' + ordinal);
175 // final int contentLength = urlConnection.getContentLength();
176 // if((contentLength < ExhibitName.MIN_NAME_LENGTH) || (contentLength > ExhibitName.MAX_NAME_LENGTH))
177 // { throw new IOException("corrupt response: Content-Length " + contentLength); }
178 //
179 // // Read and parse response.
180 // final InputStream inputStream = urlConnection.getInputStream();
181 // try
182 // {
183 // final DataInputStream dis = new DataInputStream(inputStream);
184 // final byte buf[] = new byte[contentLength];
185 // dis.readFully(buf);
186 // final StringBuilder sbi = new StringBuilder(contentLength);
187 // for(int i = 0; i < contentLength; ++i)
188 // { sbi.append((char) buf[i]); }
189 // final String s = sbi.toString();
190 // if(!ExhibitName.validNameSyntax(s))
191 // { throw new IOException("corrupt exhibit name received"); }
192 // return(s);
193 // }
194 // finally
195 // { inputStream.close(); /* Free resources. */ }
196 // }
197
198 /**Client-side call to get exhibit names (by index) from the server; never null.
199 *
200 * @param hostAndPort server host with optional port,
201 * or null to use the default/main server name
202 *
203 * @throws java.io.IOException in case of error
204 */
205 static final Name.ExhibitFull[] getExhibitNames(URL hostAndPort, final int ordinals[])
206 throws IOException
207 {
208 if((ordinals == null) || (ordinals.length > LightweightMetaDataFetchInterface.MAX_NAMES_REQUEST))
209 { throw new IllegalArgumentException(); }
210
211 if(hostAndPort == null) { hostAndPort = new URL("http", CoreConsts.MAIN_DATA_HOST, "/"); }
212
213 final StringBuilder req = new StringBuilder(64);
214 req.append(SERVLET_MOUNT_POINT + '?' + HP_OPCODE + '=' + OP_LUS + '&' + HP_ORD + '=');
215 for(int i = 0; i < ordinals.length; ++i)
216 {
217 final int ordinal = ordinals[i];
218 if(ordinal < 0)
219 { throw new IllegalArgumentException(); }
220 if(i != 0) { req.append(','); }
221 req.append(ordinal);
222 }
223 final HttpURLConnection urlConnection = _setUpConnection(hostAndPort, req.toString());
224 final int contentLength = urlConnection.getContentLength();
225 if((contentLength < ExhibitName.MIN_NAME_LENGTH) || (contentLength >= (1+ExhibitName.MAX_NAME_LENGTH)*LightweightMetaDataFetchInterface.MAX_NAMES_REQUEST))
226 { throw new IOException("corrupt response: Content-Length " + contentLength); }
227
228 // Read and parse response.
229 final InputStream inputStream = urlConnection.getInputStream();
230 try
231 {
232 final DataInputStream dis = new DataInputStream(inputStream);
233 final byte buf[] = new byte[contentLength];
234 dis.readFully(buf);
235 final StringBuilder sbi = new StringBuilder(contentLength);
236 for(int i = 0; i < contentLength; ++i)
237 { sbi.append((char) buf[i]); }
238 final Name.ExhibitFull results[] = new Name.ExhibitFull[ordinals.length];
239 final String s = sbi.toString();
240 final String names[] = s.split(",");
241 Name.ExhibitFull prev = null; // 'Previous' ExhibitFull to help share prefixes.
242 for(int i = Math.min(names.length, results.length); --i >= 0; )
243 {
244 final String n = names[i];
245 if(!ExhibitName.validNameSyntax(n))
246 { throw new IOException("corrupt exhibit name received"); }
247 prev = results[i] = Name.ExhibitFull.create(n, prev);
248 }
249 return(results);
250 }
251 finally
252 { inputStream.close(); /* Free resources. */ }
253 }
254 }