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        }