001: package org.jasig.portal.channels.jsp;
002:
003: import java.io.File;
004: import java.io.FileOutputStream;
005: import java.io.IOException;
006: import java.io.InputStream;
007: import java.io.OutputStream;
008: import java.net.URL;
009: import java.util.ArrayList;
010: import java.util.Enumeration;
011: import java.util.Iterator;
012: import java.util.List;
013: import java.util.jar.JarFile;
014: import java.util.zip.ZipEntry;
015:
016: import javax.servlet.ServletContext;
017:
018: import org.apache.commons.logging.Log;
019: import org.apache.commons.logging.LogFactory;
020: import org.jasig.portal.PortalException;
021: import org.jasig.portal.PortalSessionManager;
022: import org.jasig.portal.car.CarResources;
023: import org.jasig.portal.properties.PropertiesManager;
024:
025: /**
026: * Determines if resources in a CAR containing JSPs need to be extracted and
027: * placed in a configurable location external to the CAR or to update resources
028: * already there. This is necessary to enable the web server to compile the
029: * JSPs and for the JSPs to be able to access classes and property files
030: * included in the CAR upon which they depend.
031: *
032: * @author Mark Boyd
033: */
034: public class Deployer {
035: private static final Log LOG = LogFactory.getLog(Deployer.class);
036: public static final String CLASSES_DEPLOY_DIR_PROP = "org.jasig.portal.channels.jsp.Deployer.context.relative.classesPath";
037: public static final String JSP_DEPLOY_DIR_PROP = "org.jasig.portal.channels.jsp.Deployer.context.relative.jspPath";
038: private String mCarFilePath;
039: private String mClassFilePath;
040: private String mClassName;
041: private boolean mDeploy;
042: private boolean mPurgeOldResources;
043: private boolean mDeployedAsCar;
044:
045: private static String cClassesDirPath = null;
046: private static String cJspDirPath = null;
047:
048: private File mCarFile = null;
049:
050: /**
051: * @param classname
052: */
053: void deployResources(String classname) throws PortalException {
054: this .mClassName = classname;
055: determineDeploymentNeeds();
056:
057: if (mDeploy)
058: extractNewResources();
059: }
060:
061: /**
062: * Passes through the resources found in the CAR containing the class
063: * passed to this deployer and places any contained .jsp or .class files
064: * into appropriate directories determined by configuration and tells the
065: * deployment specification this information so that it can update its
066: * references.
067: */
068: private void extractNewResources() throws PortalException {
069: getClassesPath();
070: getJspPath();
071: JarFile jar = getCar();
072: List resources = new ArrayList();
073:
074: purgePreviousResourcesForCar();
075:
076: for (Enumeration entries = jar.entries(); entries
077: .hasMoreElements();) {
078: ZipEntry z = (ZipEntry) entries.nextElement();
079: String resource = z.getName();
080: if (resource.endsWith(".jsp") || resource.endsWith(".jspf")
081: || resource.endsWith(".jsf")
082: || resource.endsWith(".class")
083: || resource.endsWith(".properties")) {
084: extractResource(z, jar);
085: resources.add(resource);
086: }
087: }
088: DeploymentSpec.getInstance().addDeploymentFor(mCarFilePath,
089: resources);
090: }
091:
092: /**
093: * Removes old entries for the CAR being deployed if any and returns the
094: * corresponding property referring to the car's info.
095: *
096: * @return
097: * @throws PortalException
098: */
099: private void purgePreviousResourcesForCar() throws PortalException {
100: // first remove from the deployment spec
101: List oldResources = DeploymentSpec.getInstance()
102: .removeEntriesFor(mCarFilePath);
103:
104: // now purge file paths and the files at those destinations as needed
105: if (mPurgeOldResources) {
106: for (Iterator itr = oldResources.iterator(); itr.hasNext();) {
107: String resource = (String) itr.next();
108: String filePath = mClassFilePath + "/" + resource;
109:
110: File theFile = new File(filePath);
111: if (theFile.exists())
112: theFile.delete();
113: }
114: }
115: }
116:
117: /**
118: * Writes the bytes for the jar file zip entry out of the jar file and
119: * into a file in the configurable classpath accessible destination area.
120: *
121: * @param z
122: */
123: private void extractResource(ZipEntry z, JarFile jar)
124: throws PortalException {
125: File destination = getOutputFileFor(z);
126: OutputStream out = null;
127: try {
128: if (LOG.isDebugEnabled())
129: LOG.debug("Attempting to open '"
130: + destination.getAbsolutePath()
131: + "' for extracting a CAR resource.");
132: out = new FileOutputStream(destination);
133: } catch (Exception e) {
134: throw new PortalException("Unable to open file '"
135: + destination.getAbsolutePath() + "'.", e);
136: }
137: byte[] bytes = new byte[4096];
138: int bytesRead;
139:
140: try {
141: InputStream in = jar.getInputStream(z);
142: bytesRead = in.read(bytes);
143: while (bytesRead != -1) {
144: out.write(bytes, 0, bytesRead);
145: bytesRead = in.read(bytes);
146: }
147: in.close();
148: out.flush();
149: out.close();
150: } catch (IOException e) {
151: throw new PortalException(
152: "Unable to extract content of '" + z.getName()
153: + "' in CAR '" + jar.getName() + "'.", e);
154: }
155: }
156:
157: /**
158: * @param z
159: */
160: private File getOutputFileFor(ZipEntry z) throws PortalException {
161: int lastSeparator = z.getName().lastIndexOf('/');
162: String fullName = "";
163: String strDirPath = cClassesDirPath;
164:
165: if (z.getName().endsWith(".jsp")
166: || z.getName().endsWith(".jspf")
167: || z.getName().endsWith(".jsf")) {
168: strDirPath = cJspDirPath;
169: }
170:
171: fullName = strDirPath + "/" + z.getName();
172:
173: String filePath = null;
174:
175: if (lastSeparator == -1) // no path only filename
176: {
177: filePath = strDirPath;
178: } else // strip off terminating filename
179: {
180: filePath = strDirPath + "/"
181: + z.getName().substring(0, lastSeparator);
182: }
183:
184: File dirPath = new File(filePath);
185: if (!dirPath.exists())
186: dirPath.mkdirs();
187:
188: try {
189: if (LOG.isDebugEnabled())
190: LOG.debug("Attempting to open '" + fullName
191: + "' for extracting a CAR resource.");
192: return new File(fullName);
193: } catch (Exception e) {
194: throw new PortalException("Unable to open file '"
195: + filePath + "'.", e);
196: }
197: }
198:
199: /**
200: * Returns a JarFile object representing the CAR that contains the class
201: * file passed into this deployer for deployment.
202: *
203: * @return JarFile representing the CAR containing the class file.
204: */
205: private JarFile getCar() throws PortalException {
206: File car = getCarFile();
207: String carFileFullPath = car.getAbsolutePath();
208: JarFile jar;
209: try {
210: jar = new JarFile(car);
211: } catch (IOException e) {
212: throw new PortalException("Unable to open CAR '"
213: + carFileFullPath + "'." + e);
214: }
215: return jar;
216: }
217:
218: /**
219: * Returns a File object representing the CAR file containing the
220: * controller class.
221: * @return
222: * @throws PortalException
223: */
224: private File getCarFile() throws PortalException {
225: if (mCarFile == null) {
226: CarResources cRes = CarResources.getInstance();
227: URL classUrl = cRes.findResource(mClassFilePath);
228: if (classUrl == null)
229: throw new PortalException(
230: "Unable to locate CAR containing compiled "
231: + "controller class file '"
232: + mClassFilePath + "'.");
233: String carPath = classUrl.toExternalForm();
234: carPath = carPath.substring("jar:file:".length());
235: carPath = carPath.substring(0, carPath.indexOf('!'));
236: mCarFile = new File(carPath);
237:
238: if (!mCarFile.exists())
239: throw new PortalException("Unable to locate CAR '"
240: + carPath + "'. Resources can't be deployed.");
241: }
242: return mCarFile;
243: }
244:
245: private String getClassesPath() throws PortalException {
246: if (cClassesDirPath == null)
247: cClassesDirPath = getRealPath(CLASSES_DEPLOY_DIR_PROP);
248:
249: return cClassesDirPath;
250: }
251:
252: private String getRealPath(String relativePath) {
253: ServletContext ctx = PortalSessionManager.getInstance()
254: .getServletContext();
255: String ctxRelativePath = PropertiesManager.getProperty(
256: relativePath, "/WEB-INF/classes");
257: if (ctxRelativePath.endsWith("/")
258: || ctxRelativePath.endsWith("\\"))
259: ctxRelativePath = ctxRelativePath.substring(0,
260: ctxRelativePath.length() - 1);
261: String realPath = ctx.getRealPath(ctxRelativePath);
262: if (realPath == null)
263: throw new PortalException("Unable to locate directory "
264: + ctxRelativePath);
265: return realPath;
266: }
267:
268: /**
269: * Returns the path to which jsps are deployed. NOTE:
270: * They are currently deployed in the same location as the
271: * rest of the car's resources, but this may change.
272: *
273: * @return The path for deploying the jsp files.
274: * @throws PortalException
275: */
276: private String getJspPath() throws PortalException {
277: if (cJspDirPath == null)
278: cJspDirPath = getRealPath(JSP_DEPLOY_DIR_PROP);
279:
280: return cJspDirPath;
281: }
282:
283: /**
284: * @param classname
285: * @return
286: */
287: private void determineDeploymentNeeds() throws PortalException {
288: mClassFilePath = mClassName.replace('.', '/') + ".class";
289: CarResources cRes = CarResources.getInstance();
290:
291: mCarFilePath = cRes.getContainingCarPath(mClassFilePath);
292: boolean classInCar = mCarFilePath != null;
293: boolean classInDir = classInDir();
294: boolean depldInDir = DeploymentSpec.getInstance()
295: .isDeployInfoAvailableFor(mClassFilePath);
296: boolean carIsNewer = depldInDir && isCarNewer();
297:
298: if (LOG.isDebugEnabled())
299: LOG.debug("classInCar: " + classInCar + " classInDir: "
300: + classInDir + " depldInDir: " + depldInDir
301: + " carIsNewer: " + carIsNewer);
302:
303: if (!classInDir && classInCar && !depldInDir) {
304: mDeployedAsCar = true;
305: mPurgeOldResources = false;
306: mDeploy = true;
307: } else if (classInDir && !classInCar && !depldInDir) {
308: mDeployedAsCar = false;
309: mPurgeOldResources = false;
310: mDeploy = false;
311: } else if (classInDir && classInCar && !depldInDir) {
312: mDeployedAsCar = true;
313: mPurgeOldResources = false;
314: mDeploy = true;
315: } else if ((!classInDir && classInCar && depldInDir)
316: || (classInDir && !classInCar && depldInDir && carIsNewer)
317: || (classInDir && classInCar && depldInDir && carIsNewer)
318: || (!classInDir && classInCar && depldInDir && !carIsNewer)) {
319: mDeployedAsCar = true;
320: mPurgeOldResources = true;
321: mDeploy = true;
322: } else if (classInDir && classInCar && depldInDir
323: && !carIsNewer) {
324: mDeployedAsCar = true;
325: mPurgeOldResources = false;
326: mDeploy = false;
327: }
328: }
329:
330: /**
331: * Determines if the resource identified by the passed in classFilePath,
332: * a value like <code>a/b/c/some.class</code>, is found on the classpath
333: * in a directory or within a Jar file. If found within a Jar the URL
334: * returned will be in Jar URL format like <code>jar:file:/D:/...</code>.
335: * If located as a regular file in a directory somewhere it will have a
336: * file URL format like <code>file:/D:/...</code>. It is important not
337: * to instantiate the class to make this
338: * determination since we don't want the class instantiated until its new
339: * version has been extracted from the CAR if needed. Once instantiated
340: * by loading from one location by a classloader it will not be loaded
341: * again without replacing that class loader instance.
342: *
343: * @param classFilePath
344: * @return
345: */
346: private boolean classInDir() throws PortalException {
347: CarResources cRes = CarResources.getInstance();
348: ClassLoader cLdr = cRes.getClassLoader();
349:
350: URL classUrl = cLdr.getResource(mClassFilePath);
351:
352: // if null then not in CAR or on classpath
353: if (classUrl == null)
354: throw new PortalException("Class file, '" + mClassFilePath
355: + "' not found on classpath.");
356:
357: String classUrlPath = classUrl.toExternalForm();
358: if (classUrlPath.startsWith("file:")) {
359: return true;
360: }
361: return false;
362: }
363:
364: /**
365: * Determines if the timestamp on the CAR from which deployment may have
366: * taken place is later than when deployment took place as defined in
367: * the deployment info file. If later, then redployment is needed since a
368: * new CAR appears to have been installed.
369: *
370: * @return
371: */
372: private boolean isCarNewer() throws PortalException {
373: long depDate = DeploymentSpec.getInstance()
374: .getTimeOfDeploymentForResource(mClassFilePath);
375: File car = getCarFile();
376: long carModDate = car.lastModified();
377: return carModDate > depDate;
378: }
379:
380: /**
381: * @return
382: */
383: boolean isDeployedInCar() {
384: return mDeployedAsCar;
385: }
386: }
|