001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.components.language.generator;
018:
019: import org.apache.avalon.framework.activity.Disposable;
020: import org.apache.avalon.framework.component.ComponentException;
021: import org.apache.avalon.framework.component.ComponentManager;
022: import org.apache.avalon.framework.component.ComponentSelector;
023: import org.apache.avalon.framework.component.Composable;
024: import org.apache.avalon.framework.component.Recomposable;
025: import org.apache.avalon.framework.context.Context;
026: import org.apache.avalon.framework.context.ContextException;
027: import org.apache.avalon.framework.context.Contextualizable;
028: import org.apache.avalon.framework.logger.AbstractLogEnabled;
029: import org.apache.avalon.framework.parameters.ParameterException;
030: import org.apache.avalon.framework.parameters.Parameterizable;
031: import org.apache.avalon.framework.parameters.Parameters;
032: import org.apache.avalon.framework.thread.ThreadSafe;
033: import org.apache.cocoon.Constants;
034: import org.apache.cocoon.ProcessingException;
035: import org.apache.cocoon.components.classloader.ClassLoaderManager;
036: import org.apache.cocoon.components.language.LanguageException;
037: import org.apache.cocoon.components.language.markup.MarkupLanguage;
038: import org.apache.cocoon.components.language.programming.CodeFormatter;
039: import org.apache.cocoon.components.language.programming.Program;
040: import org.apache.cocoon.components.language.programming.ProgrammingLanguage;
041: import org.apache.cocoon.environment.SourceResolver;
042: import org.apache.cocoon.util.IOUtils;
043: import org.apache.excalibur.source.Source;
044:
045: import java.io.File;
046: import java.net.MalformedURLException;
047:
048: /**
049: * The default implementation of <code>ProgramGenerator</code>
050: *
051: * @author <a href="mailto:ricardo@apache.org">Ricardo Rocha</a>
052: * @author <a href="mailto:vgritsenko@apache.org">Vadim Gritsenko</a>
053: * @author <a href="mailto:tcurdt@apache.org">Torsten Curdt</a>
054: * @version CVS $Id: ProgramGeneratorImpl.java 433543 2006-08-22 06:22:54Z crossley $
055: */
056: public class ProgramGeneratorImpl extends AbstractLogEnabled implements
057: ProgramGenerator, Contextualizable, Composable,
058: Parameterizable, Disposable, ThreadSafe {
059:
060: /** The auto-reloading option */
061: protected boolean autoReload = true;
062:
063: /** The pre-loading option */
064: protected boolean preload = false;
065:
066: /** The check for manual source changes in the repository*/
067: protected boolean watchSource = false;
068:
069: /**
070: * The ComponentSelector for programs. Caches Program by program
071: * source file.
072: */
073: protected GeneratorSelector cache;
074:
075: /** The component manager */
076: protected ComponentManager manager;
077:
078: /** The markup language component selector */
079: protected ComponentSelector markupSelector;
080:
081: /** The programming language component selector */
082: protected ComponentSelector languageSelector;
083:
084: /** The working directory */
085: protected File workDir;
086:
087: /** The ClassLoaderManager */
088: protected ClassLoaderManager classManager;
089:
090: /** The root package */
091: protected String rootPackage;
092:
093: /** Servlet Context Directory */
094: protected String contextDir;
095:
096: /** Contextualize this class */
097: public void contextualize(Context context) throws ContextException {
098: if (this .workDir == null) {
099: this .workDir = (File) context
100: .get(Constants.CONTEXT_WORK_DIR);
101: }
102:
103: if (this .contextDir == null) {
104: org.apache.cocoon.environment.Context ctx = (org.apache.cocoon.environment.Context) context
105: .get(Constants.CONTEXT_ENVIRONMENT_CONTEXT);
106:
107: // Determine the context directory, preferably as a file
108: // FIXME (SW) - this is purposely redundant with some code in CocoonServlet
109: // to have the same rootPath. How to avoid this ?
110: try {
111: String rootPath = ctx.getRealPath("/");
112: if (rootPath != null) {
113: this .contextDir = new File(rootPath).toURL()
114: .toExternalForm();
115: } else {
116: String webInf = ctx.getResource("/WEB-INF")
117: .toExternalForm();
118: this .contextDir = webInf.substring(0, webInf
119: .length()
120: - "WEB-INF".length());
121: }
122: if (getLogger().isDebugEnabled()) {
123: getLogger().debug(
124: "Context directory is " + this .contextDir);
125: }
126: } catch (MalformedURLException e) {
127: getLogger().warn("Could not get context directory", e);
128: this .contextDir = "";
129: }
130: }
131: }
132:
133: /**
134: * Set the global component manager. This method also sets the
135: * <code>ComponentSelector</code> used as language factory for both markup
136: * and programming languages.
137: * @param manager The global component manager
138: */
139: public void compose(ComponentManager manager)
140: throws ComponentException {
141: if (this .manager == null && manager != null) {
142: this .manager = manager;
143: this .cache = (GeneratorSelector) this .manager
144: .lookup(GeneratorSelector.ROLE + "Selector");
145: this .markupSelector = (ComponentSelector) this .manager
146: .lookup(MarkupLanguage.ROLE + "Selector");
147: this .languageSelector = (ComponentSelector) this .manager
148: .lookup(ProgrammingLanguage.ROLE + "Selector");
149: this .classManager = (ClassLoaderManager) this .manager
150: .lookup(ClassLoaderManager.ROLE);
151: }
152: }
153:
154: /**
155: * Set the sitemap-provided configuration. This method sets the persistent code repository and the auto-reload option
156: * @param params The configuration information
157: * @exception ParameterException Not thrown here
158: */
159: public void parameterize(Parameters params)
160: throws ParameterException {
161: this .autoReload = params.getParameterAsBoolean("auto-reload",
162: autoReload);
163: this .rootPackage = params.getParameter("root-package",
164: "org.apache.cocoon.www");
165: this .preload = params.getParameterAsBoolean("preload", preload);
166: this .watchSource = params.getParameterAsBoolean("watch-source",
167: watchSource);
168: }
169:
170: /**
171: * Generates program source file name in the working directory
172: * from the source SystemID
173: */
174: private String getNormalizedName(final String systemId) {
175: StringBuffer contextFilename = new StringBuffer(
176: this .rootPackage.replace('.', File.separatorChar));
177: contextFilename.append(File.separator);
178: if (systemId.startsWith(this .contextDir)) {
179: // VG: File is located under contextDir; using relative file name ...
180: contextFilename.append(systemId.substring(this .contextDir
181: .length()));
182: } else {
183: // VG: File is located outside of contextDir; using systemId ...
184: contextFilename.append(systemId);
185: }
186: return IOUtils.normalizedFilename(contextFilename.toString());
187: }
188:
189: /**
190: * Load a program built from an XML document written in a <code>MarkupLanguage</code>
191: *
192: * @param fileName The input document's <code>File</code>
193: * @param markupLanguageName The <code>MarkupLanguage</code> in which the input document is written
194: * @param programmingLanguageName The <code>ProgrammingLanguage</code> in which the program must be written
195: * @return The loaded program instance
196: * @exception Exception If an error occurs during generation or loading
197: * @deprecated Pass Source object instead of file name.
198: */
199: public CompiledComponent load(ComponentManager newManager,
200: String fileName, String markupLanguageName,
201: String programmingLanguageName, SourceResolver resolver)
202: throws Exception {
203:
204: final Source source = resolver.resolveURI(fileName);
205: try {
206: return load(newManager, source, markupLanguageName,
207: programmingLanguageName, resolver);
208: } finally {
209: resolver.release(source);
210: }
211: }
212:
213: /**
214: * Load a program built from an XML document written in a <code>MarkupLanguage</code>.
215: *
216: * This method does not releases passed source object. Caller of the method must release
217: * source when needed.
218: *
219: * @param source The input document's <code>File</code>
220: * @param markupLanguageName The <code>MarkupLanguage</code> in which the input document is written
221: * @param programmingLanguageName The <code>ProgrammingLanguage</code> in which the program must be written
222: * @return The loaded program instance
223: * @exception Exception If an error occurs during generation or loading
224: */
225: public CompiledComponent load(ComponentManager newManager,
226: Source source, String markupLanguageName,
227: String programmingLanguageName, SourceResolver resolver)
228: throws Exception {
229:
230: final String id = source.getURI();
231:
232: ProgrammingLanguage programmingLanguage = null;
233: MarkupLanguage markupLanguage = null;
234: try {
235: // Create file name for the program generated from the provided source.
236: final String normalizedName = getNormalizedName(id);
237:
238: if (getLogger().isDebugEnabled()) {
239: getLogger().debug(
240: "Loading serverpage systemId=[" + id + "]"
241: + " markupLanguageName=["
242: + markupLanguageName + "]"
243: + " programmingLanguageName=["
244: + programmingLanguageName + "]"
245: + " -> normalizedName=["
246: + normalizedName + "]");
247: }
248:
249: markupLanguage = (MarkupLanguage) this .markupSelector
250: .select(markupLanguageName);
251: programmingLanguage = (ProgrammingLanguage) this .languageSelector
252: .select(programmingLanguageName);
253: programmingLanguage
254: .setLanguageName(programmingLanguageName);
255:
256: Program program = null;
257: CompiledComponent programInstance = null;
258:
259: // Attempt to load program object from cache
260: try {
261: programInstance = (CompiledComponent) this .cache
262: .select(normalizedName);
263: } catch (Exception e) {
264: if (getLogger().isDebugEnabled()) {
265: getLogger().debug(
266: "The serverpage [" + id
267: + "] is not in the cache yet");
268: }
269: }
270:
271: if (programInstance == null && this .preload) {
272: // Preloading: Load program if its source/[object file] is available
273: try {
274: program = programmingLanguage.preload(
275: normalizedName, this .workDir,
276: markupLanguage.getEncoding());
277:
278: this .cache.addGenerator(newManager, normalizedName,
279: program);
280: programInstance = (CompiledComponent) this .cache
281: .select(normalizedName);
282:
283: if (getLogger().isDebugEnabled()) {
284: getLogger().debug(
285: "Successfully preloaded serverpage ["
286: + id + "]");
287: }
288: } catch (Exception e) {
289: if (getLogger().isInfoEnabled()) {
290: getLogger()
291: .info(
292: "The serverpage ["
293: + id
294: + "] could not be preloaded, will be re-created ("
295: + e + ")");
296: }
297: }
298: }
299:
300: if (programInstance == null) {
301: synchronized (this ) {
302: // Attempt again to load program object from cache.
303: // This avoids that simultaneous requests recompile
304: // the same XSP over and over again.
305: try {
306: programInstance = (CompiledComponent) this .cache
307: .select(normalizedName);
308: if (getLogger().isDebugEnabled()) {
309: getLogger().debug(
310: "The serverpage [" + id
311: + "] was now in the cache");
312: }
313: } catch (Exception e) {
314: // no instance found
315: if (getLogger().isDebugEnabled()) {
316: getLogger().debug(
317: "Creating new serverpage for ["
318: + id + "]");
319: }
320: generateSourcecode(source, normalizedName,
321: markupLanguage, programmingLanguage);
322:
323: programInstance = loadProgram(newManager,
324: normalizedName, markupLanguage,
325: programmingLanguage);
326: }
327: }
328: } else {
329: // found an instance
330: if (this .autoReload) {
331: long sourceLastModified = source.getLastModified();
332: // Has XSP changed?
333: // Note : lastModified can be 0 if source is dynamically generated.
334: // In that case, let the program instance decide if it is modified or not.
335: if (programInstance
336: .modifiedSince(sourceLastModified)) {
337: if (getLogger().isDebugEnabled()) {
338: getLogger().debug(
339: "ReCreating serverpage for [" + id
340: + "]");
341: }
342: synchronized (this ) {
343: if (getLogger().isDebugEnabled()) {
344: getLogger().debug(
345: "Releasing old serverpage program ["
346: + id + "]");
347: }
348: release(programInstance);
349: programmingLanguage.unload(program,
350: normalizedName, this .workDir);
351: this .cache.removeGenerator(normalizedName);
352: programInstance = null;
353: program = null;
354:
355: generateSourcecode(source, normalizedName,
356: markupLanguage, programmingLanguage);
357:
358: programInstance = loadProgram(newManager,
359: normalizedName, markupLanguage,
360: programmingLanguage);
361: }
362: } else {
363: // check the repository for changes at all?
364: if (this .watchSource) {
365: if (getLogger().isDebugEnabled()) {
366: getLogger().debug(
367: "Checking sourcecode of [" + id
368: + "] for a change");
369: }
370: File sourcecodeFile = new File(
371: this .workDir,
372: normalizedName
373: + "."
374: + programmingLanguage
375: .getSourceExtension());
376: // has sourcecode in repository changed ?
377: if (sourcecodeFile != null
378: && sourcecodeFile.exists()) {
379: long sourcecodeLastModified = sourcecodeFile
380: .lastModified();
381: if (sourcecodeLastModified > sourceLastModified
382: || sourceLastModified == 0
383: || sourcecodeLastModified == 0) {
384: if (getLogger().isDebugEnabled()) {
385: getLogger()
386: .debug(
387: "Create new serverpage program for ["
388: + id
389: + "] - repository has changed");
390: }
391: synchronized (this ) {
392: if (getLogger()
393: .isDebugEnabled()) {
394: getLogger().debug(
395: "Releasing old serverpage program ["
396: + id + "]");
397: }
398: release(programInstance);
399: //programmingLanguage.unload(program, normalizedName, this.workDir);
400: this .cache
401: .removeGenerator(normalizedName);
402: programInstance = null;
403: program = null;
404:
405: programInstance = loadProgram(
406: newManager,
407: normalizedName,
408: markupLanguage,
409: programmingLanguage);
410: }
411: } else {
412: if (getLogger().isDebugEnabled()) {
413: getLogger()
414: .debug(
415: "Sourcecode of ["
416: + id
417: + "] has not changed - returning program from cache");
418: }
419: }
420: } else {
421: if (getLogger().isErrorEnabled()) {
422: getLogger().error(
423: "Could not find sourcecode for ["
424: + id + "]");
425: }
426: }
427: }
428: }
429: } else {
430: if (getLogger().isDebugEnabled()) {
431: getLogger()
432: .debug(
433: "Not checking for modifications [autoReload=false] - using current version");
434: }
435: }
436: }
437:
438: // Recompose with the new manager if program needs it.
439: // This is required to provide XSP with manager from the correct
440: // sitemap so it will be able to find all components declared in
441: // the sitemap.
442: if (programInstance instanceof Recomposable) {
443: ((Recomposable) programInstance).recompose(newManager);
444: }
445:
446: return (programInstance);
447: } finally {
448: this .markupSelector.release(markupLanguage);
449: this .languageSelector.release(programmingLanguage);
450: }
451: }
452:
453: private CompiledComponent loadProgram(ComponentManager newManager,
454: String normalizedName, MarkupLanguage markupLanguage,
455: ProgrammingLanguage programmingLanguage) throws Exception {
456:
457: CompiledComponent programInstance = null;
458:
459: try {
460: return (CompiledComponent) this .cache
461: .select(normalizedName);
462: } catch (Exception e) {
463: }
464:
465: try {
466: if (getLogger().isDebugEnabled()) {
467: getLogger().debug(
468: "Loading program [" + normalizedName + "]");
469: }
470: Program program = programmingLanguage.load(normalizedName,
471: this .workDir, markupLanguage.getEncoding());
472:
473: this .cache
474: .addGenerator(newManager, normalizedName, program);
475: if (getLogger().isDebugEnabled()) {
476: getLogger().debug(
477: "Successfully loaded program ["
478: + normalizedName + "]");
479: }
480: } catch (LanguageException le) {
481: if (getLogger().isDebugEnabled()) {
482: getLogger().debug("Got Language Exception", le);
483: }
484: throw new ProcessingException("Language Exception", le);
485: }
486:
487: try {
488: programInstance = (CompiledComponent) this .cache
489: .select(normalizedName);
490: } catch (Exception cme) {
491: if (getLogger().isDebugEnabled()) {
492: getLogger().debug(
493: "Can't load ServerPage: got exception", cme);
494: }
495: throw new ProcessingException("Can't load ServerPage", cme);
496: }
497:
498: return (programInstance);
499: }
500:
501: private void generateSourcecode(Source source,
502: String normalizedName, MarkupLanguage markupLanguage,
503: ProgrammingLanguage programmingLanguage) throws Exception {
504:
505: if (getLogger().isDebugEnabled()) {
506: getLogger()
507: .debug(
508: "Creating sourcecode for ["
509: + source.getURI() + "]");
510: }
511:
512: // Generate code
513: String code = markupLanguage.generateCode(source,
514: normalizedName, programmingLanguage);
515: if (code == null || code.length() == 0) {
516: // FIXME(VG): Xalan with incremental-processing=true does not propagate exceptions
517: // from working thread to main thread. See
518: // http://nagoya.apache.org/bugzilla/show_bug.cgi?id=8033
519: throw new ProcessingException(
520: "Failed to generate program code (this may happen "
521: + "if you use Xalan in incremental processing mode). "
522: + "Please check log file and/or console for errors.");
523: }
524:
525: String encoding = markupLanguage.getEncoding();
526:
527: // Format source code if applicable
528: CodeFormatter codeFormatter = programmingLanguage
529: .getCodeFormatter();
530: if (codeFormatter != null) {
531: code = codeFormatter.format(code, encoding);
532: }
533:
534: // Store generated code
535: final File sourceFile = new File(this .workDir, normalizedName
536: + "." + programmingLanguage.getSourceExtension());
537: final File sourceDir = sourceFile.getParentFile();
538: if (sourceDir != null) {
539: sourceDir.mkdirs();
540: }
541: IOUtils.serializeString(sourceFile, code);
542: if (getLogger().isDebugEnabled()) {
543: getLogger().debug(
544: "Successfully created sourcecode for ["
545: + source.getURI() + "]");
546: }
547: }
548:
549: /**
550: * Releases the program instance.
551: * @param component program instance to be released
552: */
553: public void release(CompiledComponent component) {
554: this .cache.release(component);
555: }
556:
557: /**
558: * Removes named program from the program generator's cache.
559: * Disposes all created instances of the program.
560: * @param source of the program to be removed
561: */
562: public void remove(Source source) {
563: final String normalizedName = getNormalizedName(source.getURI());
564: this .cache.removeGenerator(normalizedName);
565: }
566:
567: /**
568: * dispose
569: */
570: public void dispose() {
571: this.manager.release(this.cache);
572: this.cache = null;
573: this.manager.release(this.markupSelector);
574: this.markupSelector = null;
575: this.manager.release(this.languageSelector);
576: this.languageSelector = null;
577: this.manager.release(this.classManager);
578: this.classManager = null;
579:
580: this.manager = null;
581:
582: this.workDir = null;
583: this.contextDir = null;
584: }
585: }
|