/* This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV This program is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program; if not, see http://www.gnu.org/licenses or write to the Free Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 */ package com.servoy.j2db; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.net.URLStreamHandler; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import org.apache.wicket.RequestCycle; import org.apache.wicket.ResourceReference; import com.servoy.j2db.dataprocessing.BufferedDataSet; import com.servoy.j2db.dataprocessing.FoundSet; import com.servoy.j2db.dataprocessing.IRecordInternal; import com.servoy.j2db.persistence.Media; import com.servoy.j2db.util.DataSourceUtils; import com.servoy.j2db.util.MimeTypes; import com.servoy.j2db.util.SafeArrayList; import com.servoy.j2db.util.Utils; /** * Class to handle the media:///<name> or media:///blob_loader?x=y urls * * @author jblok */ public class MediaURLStreamHandler extends URLStreamHandler { public static final String MEDIA_URL_DEF = "media:///"; //$NON-NLS-1$ public static final String MEDIA_URL_BLOBLOADER = "servoy_blobloader"; //$NON-NLS-1$ final IServiceProvider application; public MediaURLStreamHandler(IServiceProvider application) { this.application = application; } public MediaURLStreamHandler() { this.application = null; } @Override public URLConnection openConnection(URL u) throws IOException { final IServiceProvider app = this.application == null ? J2DBGlobals.getServiceProvider() : this.application; String fname = u.getFile(); if (fname.startsWith("/")) //$NON-NLS-1$ { fname = fname.substring(1); } if (fname.indexOf("?") != -1) //$NON-NLS-1$ { fname = fname.substring(0, fname.indexOf("?")); } final String filename = fname; if (filename.startsWith(MEDIA_URL_BLOBLOADER)) { URLConnection urlc = new URLConnection(u) { private byte[] array; private String mimeType = "application/binary"; //$NON-NLS-1$ @Override public void connect() throws IOException { if (url != null) { array = getBlobLoaderMedia(app, url.getQuery()); connected = array != null; mimeType = getBlobLoaderMimeType(url.getQuery()); } } @Override public String getHeaderField(String name) { if ("content-length".equals(name)) //$NON-NLS-1$ { if (array == null) { return "0"; //$NON-NLS-1$ } else { return Integer.toString(array.length); } } else if ("content-type".equals(name)) //$NON-NLS-1$ { if (mimeType == null && array != null) { return MimeTypes.getContentType(array); } else { return mimeType; } } return super.getHeaderField(name); } @Override public InputStream getInputStream() throws IOException { if (!connected) connect(); if (array != null) { ByteArrayInputStream bais = new ByteArrayInputStream(array); return bais; } else { return new InputStream() { @Override public int available() throws IOException { return 0; } @Override public int read() throws IOException { return -1; } }; } } @Override public String toString() { return "MediaURL:" + url; //$NON-NLS-1$ } }; return urlc; } else { URLConnection urlc = new HttpURLConnection(u) { private Media m; private byte[] array; private String etag; private boolean useCachedVersion = false; @Override public void connect() throws IOException { m = getMedia(filename, app); if (m != null) { array = m.getMediaData(); etag = getRequestProperty("If-None-Match"); if (("" + array.hashCode()).equals(etag)) { array = null; useCachedVersion = true; } else { etag = "" + array.hashCode(); } connected = true; } } @Override public String getHeaderField(String name) { if ("content-length".equals(name)) //$NON-NLS-1$ { if (array == null) { return "0"; //$NON-NLS-1$ } else { return Integer.toString(array.length); } } else if ("content-type".equals(name)) //$NON-NLS-1$ { if (m == null) { return null; } else { return m.getMimeType(); } } return super.getHeaderField(name); } @Override public InputStream getInputStream() throws IOException { if (!connected) connect(); if (array != null) { ByteArrayInputStream bais = new ByteArrayInputStream(array); return bais; } else { return new InputStream() { @Override public int available() throws IOException { return 0; } @Override public int read() throws IOException { return -1; } }; } } @Override public String toString() { return "MediaURL:" + url; //$NON-NLS-1$ } @Override public String getHeaderField(int n) { if (n == 0) { return "HTTP/1.1 200 OK"; } return super.getHeaderField(n); } @Override public Map<String, List<String>> getHeaderFields() { HashMap<String, List<String>> mp = new HashMap<String, List<String>>(); ArrayList<String> al; al = new ArrayList<String>(); al.add(etag); mp.put("ETag", al); //$NON-NLS-1$ al = new ArrayList<String>(); al.add("no-cache"); //$NON-NLS-1$ mp.put("Cache-Control", al); //$NON-NLS-1$ return mp; } @Override public int getResponseCode() throws IOException { if (useCachedVersion) { return HTTP_NOT_MODIFIED; } else if (m == null) { return HTTP_NOT_FOUND; } return HTTP_OK; } @Override public void disconnect() { } @Override public boolean usingProxy() { return false; } }; return urlc; } } public Media getMedia(String name, IServiceProvider app) { if (app == null) { return null; } FlattenedSolution s = app.getFlattenedSolution(); if (s != null) { return s.getMedia(name); } return null; } public static byte[] getBlobLoaderMedia(IServiceProvider application, String urlQueryPart) throws IOException { if (application.getSolution() == null) return null;//cannot work without a solution String datasource = null; String serverName = null; String tableName = null; try { if (urlQueryPart == null) return null; String dataProvider = null; List<String> pks = new SafeArrayList<String>(); StringTokenizer tk = new StringTokenizer(urlQueryPart, "?&"); //$NON-NLS-1$ while (tk.hasMoreTokens()) { String token = tk.nextToken(); if (token.startsWith("global=")) //$NON-NLS-1$ { String globalName = token.substring("global=".length()); //$NON-NLS-1$ Object obj = application.getScriptEngine().getScopesScope().get(null, globalName); if (obj instanceof byte[]) { return (byte[])obj; } if (obj instanceof String) { // TODO check can we always just convert to the default encoding of this machine (server if web) return ((String)obj).getBytes(); } return null; } if (token.startsWith("servername=")) //$NON-NLS-1$ { serverName = token.substring("servername=".length()); //$NON-NLS-1$ } else if (token.startsWith("tablename=")) //$NON-NLS-1$ { tableName = token.substring("tablename=".length()); //$NON-NLS-1$ } else if (token.startsWith("datasource=")) //$NON-NLS-1$ { datasource = token.substring("datasource=".length()); //$NON-NLS-1$ } else if (token.startsWith("dataprovider=")) //$NON-NLS-1$ { dataProvider = token.substring("dataprovider=".length()); //$NON-NLS-1$ } else if (token.startsWith("rowid")) //$NON-NLS-1$ { int index = Utils.getAsInteger(token.substring(5, 6));//get id if (index > 0) { pks.add(index - 1, token.substring(7));//get value after 'rowidX=' } else { pks.add(0, token.substring(6));//get value after 'rowid=' } } } if (datasource == null && serverName != null && tableName != null) { datasource = DataSourceUtils.createDBTableDataSource(serverName, tableName); } if (application.getFoundSetManager().getTable(datasource) == null) { throw new IOException(Messages.getString("servoy.exception.serverAndTableNotFound", DataSourceUtils.getDBServernameTablename(datasource))); //$NON-NLS-1$ } FoundSet fs = (FoundSet)application.getFoundSetManager().getNewFoundSet(datasource); List<Object[]> rows = new ArrayList<Object[]>(1); // use mutable list here, elements are overwritten with Column.getAsRightType equivalent rows.add(pks.toArray()); if (!fs.loadExternalPKList(new BufferedDataSet(null, null, rows))) { return null; } IRecordInternal rec = fs.getRecord(0); if (rec == null) { return null; } Object blob_value = rec.getValue(dataProvider); if (blob_value instanceof byte[]) { return (byte[])blob_value; } if (blob_value instanceof String) { // TODO check can we always just convert to the default encoding of this machine (server if web) return ((String)blob_value).getBytes(); } } catch (Exception e) { throw new IOException(e.getMessage()); } return null; } public static String getBlobLoaderMimeType(String urlQueryPart) { if (urlQueryPart == null) return null; StringTokenizer tk = new StringTokenizer(urlQueryPart, "?&"); //$NON-NLS-1$ while (tk.hasMoreTokens()) { String token = tk.nextToken(); if (token.startsWith("mimetype=")) //$NON-NLS-1$ { return token.substring("mimetype=".length()); //$NON-NLS-1$ } } return null; } /** * @param url * @return */ public static String getBlobLoaderFileName(String urlQueryPart) { if (urlQueryPart == null) return null; StringTokenizer tk = new StringTokenizer(urlQueryPart, "?&"); //$NON-NLS-1$ while (tk.hasMoreTokens()) { String token = tk.nextToken(); if (token.startsWith("filename=")) //$NON-NLS-1$ { return token.substring("filename=".length()); //$NON-NLS-1$ } } return null; } /*** * gets translated relative URL from media:///name to resources/servoy/media?s=sol_name&id=name * used in web client * @param solution * @param url * @return */ public static String getTranslatedMediaURL(FlattenedSolution solution, String url) { ResourceReference rr = new ResourceReference("media"); //$NON-NLS-1$ String lowercase = url.toLowerCase(); if (lowercase.startsWith(MediaURLStreamHandler.MEDIA_URL_DEF)) { String name = url.substring(MediaURLStreamHandler.MEDIA_URL_DEF.length()); Media media = solution.getMedia(name); if (media != null) { return RequestCycle.get().urlFor(rr) + "?id=" + media.getName() + "&s=" + solution.getSolution().getName(); } } return null; } }