/********************************************************************************** * $URL:https://source.sakaiproject.org/svn/osp/trunk/presentation/api-impl/src/java/org/theospi/portfolio/presentation/export/PortfolioMirror.java $ * $Id:PortfolioMirror.java 9134 2006-05-08 20:28:42Z chmaurer@iupui.edu $ *********************************************************************************** * * Copyright (c) 2005, 2006, 2007, 2008 The Sakai Foundation * * Licensed under the Educational Community License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.opensource.org/licenses/ECL-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * **********************************************************************************/ package org.theospi.portfolio.presentation.export; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import websphinx.*; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.Vector; public class PortfolioMirror extends Mirror { protected final transient Log logger = LogFactory.getLog(getClass()); private URL base = null; private boolean needRewrite = false; Vector files = new Vector (); private String webappName = null; protected static final String ESCAPE_URL = " "; private static final String OSP_LIBRARY_PATH = "/osp-common-tool/"; public PortfolioMirror(String directory, String webappName) throws IOException { super(directory); base = new File(directory).toURL(); this.webappName = webappName; } /** * Write a page to the mirror. Stores the page on the local * disk, fixing up its links to point to the local * copies of any pages already stored to this mirror. * * @param page Page to write */ public synchronized void writePage(Page page) throws IOException { URL pageURL = page.getURL(); String local = toLocalFileURL (pageURL); URL localURL = new URL (escapeUrl(local)); File localFile = Link.URLToFile (localURL); File parent = new File (localFile.getParent ()); if (parent != null) Access.getAccess ().makeDir (parent); PortfolioMirrorTransformer out = new PortfolioMirrorTransformer (this, localFile); out.setBase (localURL); out.setEmitBaseElement (getEmitBaseElement ()); out.writePage (page); out.close (); needRewrite = !files.isEmpty (); files.addElement (out); } /** * Get number of pages written to this mirror. * @return number of calls to writePage() on this mirror */ public synchronized int getPageCount () { return files.size (); } /** * Rewrite the mirror to make local links consistent. */ public synchronized void rewrite () throws IOException { if (needRewrite) { for (int i=0, n = files.size (); i < n; ++i) { RewritableLinkTransformer r = (RewritableLinkTransformer)files.elementAt (i); r.rewrite (); } needRewrite = false; } } // maps a remote URL to a local file URL ("<root>/<host>/<filename>") // resulting URL is never slash-terminated protected String toLocalFileURL (URL remoteURL) { if (isMapped (remoteURL)) return lookup (null, remoteURL); String local; if (remoteURL.getFile().startsWith("/access")) { File file = new File(remoteURL.getPath()); local = base + webappName + "/repository/" + file.getName(); local = ensureUnique(local); } else if (remoteURL.getFile().startsWith(OSP_LIBRARY_PATH)) { String fileName = "/library/" + remoteURL.getFile().substring(OSP_LIBRARY_PATH.length()); local = base + webappName + encode(fileName); local = ensureUnique(local); } else if (remoteURL.getFile().startsWith(webappName + "/viewPresentation.osp?")) { local = base + webappName + "/myPresentation.html"; local = ensureUnique(local); } else if (!remoteURL.getFile().startsWith(webappName + "/")) { local = base + webappName + encode(remoteURL.getFile()); } else { local = base + encode(remoteURL.getFile()); } map (remoteURL, local); return local; } protected String ensureUnique(String local) { String orig = local; File file = null; try { file = new File(new URI(encode(local))); int current = 0; while (file.exists()) { current++; int dotPos = orig.lastIndexOf('.'); if (dotPos == -1) { local = orig + current; } else { local = orig.substring(0, dotPos) + "-" + current + orig.substring(dotPos); } file = new File(new URI(local)); } } catch (URISyntaxException e) { throw new RuntimeException(e); } return local; } private static String encode (String component) { char[] chars = component.toCharArray (); for (int i=0; i<chars.length; ++i) switch (chars[i]) { case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '/': case '.': case '-': case '_': case '~': case ':': break; default: chars[i] = '_'; break; } String newString = new String(chars); return newString; } private class PortfolioMirrorTransformer extends RewritableLinkTransformer { private PortfolioMirror mirror; // on the wall? private File file; public PortfolioMirrorTransformer (PortfolioMirror mirror, File file) throws IOException { super (file.toString()); this.mirror = mirror; this.file = file; } public String lookup (URL base, URL url) { try { String newURL = mirror.lookup (base, new URL(escapeUrl(url.toString()))); if (newURL.indexOf("http") < 0){ return newURL; } else { return mirror.lookup (base, url); } } catch (MalformedURLException e) { throw new RuntimeException(e); } } public void map (URL remoteURL, String href) { mirror.map (remoteURL, href); } public void map (URL remoteURL, URL url) { mirror.map (remoteURL, url); } public boolean isMapped (URL url) { return mirror.isMapped (url); } /** * Write a page through the transformer. If * getEmitBaseElement() is true and getBase() is * non-null, then the transformer * outputs a <BASE> element either inside the * page's <HEAD> element (if present) or before * the first tag that belongs in <BODY>. * * @param page Page to write */ public synchronized void writePage(Page page) throws IOException { if (page instanceof StreamedPage) { StreamedPage sp = (StreamedPage)page; FileOutputStream fos = null; try { fos = new FileOutputStream(file); byte[] buffer = new byte[PresentationExport.BUFFER]; InputStream is = sp.getStream(); int count; while ((count = is.read(buffer, 0, PresentationExport.BUFFER)) != -1) { fos.write(buffer, 0, count); } } finally { try { fos.close(); } catch (Exception e) { logger.warn("Error cleaning up resource: ", e); } } } else { super.writePage(page); } } } public static String escapeUrl(String id) { if (id == null) return ""; try { // convert the string to bytes in UTF-8 byte[] bytes = id.getBytes("UTF-8"); StringBuilder buf = new StringBuilder(); for (int i = 0; i < bytes.length; i++) { byte b = bytes[i]; // escape ascii control characters, ascii high bits, specials if ((ESCAPE_URL.indexOf((char) b) != -1) || (b <= 0x1F) || (b == 0x7F) || (b >= 0x80)) { buf.append("%"); buf.append(toHex(b)); } else { buf.append((char) b); } } String rv = buf.toString(); return rv; } catch (Exception e) { return id; } } // escapeUrl private static final String toHex(byte b) { char ret[] = new char[2]; ret[0] = hexDigit((b >>> 4) & (byte) 0x0F); ret[1] = hexDigit((b >>> 0) & (byte) 0x0F); return new String(ret); } /** * Returns the hex digit cooresponding to a number between 0 and 15. * * @param i * The number to get the hex digit for. * @return The hex digit cooresponding to that number. * @exception java.lang.IllegalArgumentException * If supplied digit is not between 0 and 15 inclusive. */ private static final char hexDigit(int i) { switch (i) { case 0: return '0'; case 1: return '1'; case 2: return '2'; case 3: return '3'; case 4: return '4'; case 5: return '5'; case 6: return '6'; case 7: return '7'; case 8: return '8'; case 9: return '9'; case 10: return 'A'; case 11: return 'B'; case 12: return 'C'; case 13: return 'D'; case 14: return 'E'; case 15: return 'F'; } throw new IllegalArgumentException("Invalid digit:" + i); } }