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.environment;
018:
019: import java.io.File;
020: import java.io.IOException;
021: import java.io.OutputStream;
022: import java.lang.reflect.Method;
023: import java.net.MalformedURLException;
024: import java.util.Enumeration;
025: import java.util.HashMap;
026: import java.util.Map;
027:
028: import org.apache.avalon.framework.CascadingRuntimeException;
029: import org.apache.avalon.framework.component.ComponentException;
030: import org.apache.avalon.framework.component.ComponentManager;
031: import org.apache.avalon.framework.logger.AbstractLogEnabled;
032: import org.apache.cocoon.Constants;
033: import org.apache.cocoon.ProcessingException;
034: import org.apache.cocoon.components.CocoonComponentManager;
035: import org.apache.cocoon.components.source.SourceUtil;
036: import org.apache.cocoon.util.BufferedOutputStream;
037: import org.apache.cocoon.util.ClassUtils;
038: import org.apache.cocoon.util.Deprecation;
039: import org.apache.commons.collections.iterators.IteratorEnumeration;
040: import org.apache.excalibur.source.SourceException;
041: import org.xml.sax.SAXException;
042:
043: /**
044: * Base class for any environment
045: *
046: * @author <a href="mailto:bluetkemeier@s-und-n.de">Björn Lütkemeier</a>
047: * @author <a href="mailto:Giacomo.Pati@pwr.ch">Giacomo Pati</a>
048: * @author <a href="mailto:cziegeler@apache.org">Carsten Ziegeler</a>
049: * @version $Id: AbstractEnvironment.java 433543 2006-08-22 06:22:54Z crossley $
050: */
051: public abstract class AbstractEnvironment extends AbstractLogEnabled
052: implements Environment {
053:
054: /** The current uri in progress */
055: protected String uris;
056:
057: /** The current prefix to strip off from the request uri */
058: protected StringBuffer prefix = new StringBuffer();
059:
060: /** The View requested */
061: protected String view;
062:
063: /** The Action requested */
064: protected String action;
065:
066: /** The Context path */
067: protected String context;
068:
069: /** The context path stored temporarily between constructor and initComponents */
070: private String tempInitContext;
071:
072: /** The root context path */
073: protected String rootContext;
074:
075: /** The servlet object model */
076: protected HashMap objectModel;
077:
078: /** The real source resolver */
079: protected org.apache.excalibur.source.SourceResolver sourceResolver;
080:
081: /** The component manager */
082: protected ComponentManager manager;
083:
084: /** The attributes */
085: private Map attributes = new HashMap();
086:
087: /** The secure Output Stream */
088: protected BufferedOutputStream secureOutputStream;
089:
090: /** The real output stream */
091: protected OutputStream outputStream;
092:
093: /** The AvalonToCocoonSourceWrapper (this is for the deprecated support) */
094: static protected Method avalonToCocoonSourceWrapper;
095:
096: /** Do we have our components ? */
097: protected boolean initializedComponents = false;
098:
099: /**
100: * Constructs the abstract environment
101: */
102: public AbstractEnvironment(String uri, String view, File file)
103: throws MalformedURLException {
104: this (uri, view, file, null);
105: }
106:
107: /**
108: * Constructs the abstract environment
109: */
110: public AbstractEnvironment(String uri, String view, File file,
111: String action) throws MalformedURLException {
112: this (uri, view, file.toURL().toExternalForm(), action);
113: }
114:
115: /**
116: * Constructs the abstract environment
117: */
118: public AbstractEnvironment(String uri, String view, String context,
119: String action) throws MalformedURLException {
120: this .uris = uri;
121: this .view = view;
122: this .tempInitContext = context;
123: this .action = action;
124: this .objectModel = new HashMap();
125: }
126:
127: /**
128: * Allow implementations to set view later than in super() constructor.
129: * View can be set only once, and should be set in implementation's constructor.
130: */
131: protected void setView(String view) {
132: if (this .view != null) {
133: throw new IllegalStateException(
134: "View was already set on this environment");
135: }
136: this .view = view;
137: }
138:
139: /**
140: * Allow implementations to set action later than in super() constructor
141: * Action can be set only once, and should be set in implementation's constructor.
142: */
143: protected void setAction(String action) {
144: if (this .action != null) {
145: throw new IllegalStateException(
146: "Action was already set on this environment");
147: }
148: this .action = action;
149: }
150:
151: /**
152: * Helper method to extract the view name from the request.
153: */
154: protected static String extractView(Request request) {
155: return request.getParameter(Constants.VIEW_PARAM);
156: }
157:
158: /**
159: * Helper method to extract the action name from the request.
160: */
161: protected static String extractAction(Request req) {
162: String action = req.getParameter(Constants.ACTION_PARAM);
163: if (action != null) {
164: /* TC: still support the deprecated syntax */
165: return action;
166: } else {
167: for (Enumeration e = req.getParameterNames(); e
168: .hasMoreElements();) {
169: String name = (String) e.nextElement();
170: if (name.startsWith(Constants.ACTION_PARAM_PREFIX)) {
171: if (name.endsWith(".x") || name.endsWith(".y")) {
172: return name.substring(
173: Constants.ACTION_PARAM_PREFIX.length(),
174: name.length() - 2);
175: } else {
176: return name
177: .substring(Constants.ACTION_PARAM_PREFIX
178: .length());
179: }
180: }
181: }
182: return null;
183: }
184: }
185:
186: // Sitemap methods
187:
188: /**
189: * Returns the uri in progress. The prefix is stripped off
190: */
191: public String getURI() {
192: return this .uris;
193: }
194:
195: /**
196: * Get the Root Context
197: */
198: public String getRootContext() {
199: if (!this .initializedComponents) {
200: this .initComponents();
201: }
202: return this .rootContext;
203: }
204:
205: /**
206: * Get the current Context
207: */
208: public String getContext() {
209: if (!this .initializedComponents) {
210: this .initComponents();
211: }
212: return this .context;
213: }
214:
215: /**
216: * Get the prefix of the URI in progress
217: */
218: public String getURIPrefix() {
219: return this .prefix.toString();
220: }
221:
222: /**
223: * Set the prefix of the URI in progress
224: */
225: protected void setURIPrefix(String prefix) {
226: if (getLogger().isDebugEnabled()) {
227: getLogger().debug(
228: "Set the URI Prefix (OLD=" + getURIPrefix()
229: + ", NEW=" + prefix + ")");
230: }
231: this .prefix = new StringBuffer(prefix);
232: }
233:
234: /**
235: * Set the context.
236: */
237: protected void setContext(String context) {
238: this .context = context;
239: }
240:
241: /**
242: * Set the context. This is similar to changeContext()
243: * except that it is absolute.
244: */
245: public void setContext(String prefix, String uri, String context) {
246: this .setContext(context);
247: this .setURIPrefix(prefix == null ? "" : prefix);
248: this .uris = uri;
249: if (getLogger().isDebugEnabled()) {
250: getLogger().debug("Reset context to " + this .context);
251: }
252: }
253:
254: /**
255: * Adds an prefix to the overall stripped off prefix from the request uri
256: */
257: public void changeContext(String newPrefix, String newContext)
258: throws IOException {
259: if (!this .initializedComponents) {
260: this .initComponents();
261: }
262:
263: if (getLogger().isDebugEnabled()) {
264: getLogger().debug("Changing Cocoon context");
265: getLogger().debug(
266: " from context(" + this .context + ") and prefix("
267: + this .prefix + ")");
268: getLogger().debug(
269: " to context(" + newContext + ") and prefix("
270: + newPrefix + ")");
271: getLogger().debug(" at URI " + this .uris);
272: }
273:
274: int l = newPrefix.length();
275: if (l >= 1) {
276: if (!this .uris.startsWith(newPrefix)) {
277: String message = "The current URI (" + this .uris
278: + ") doesn't start with given prefix ("
279: + newPrefix + ")";
280: getLogger().error(message);
281: throw new RuntimeException(message);
282: }
283: this .prefix.append(newPrefix);
284: this .uris = this .uris.substring(l);
285:
286: // check for a slash at the beginning to avoid problems with subsitemaps
287: if (this .uris.startsWith("/")) {
288: this .uris = this .uris.substring(1);
289: this .prefix.append('/');
290: }
291: }
292:
293: if (this .context.startsWith("zip:")) {
294: // if the resource is zipped into a war file (e.g. Weblogic temp deployment)
295: // FIXME (VG): Is this still required? Better to unify both cases.
296: if (getLogger().isDebugEnabled()) {
297: getLogger().debug(
298: "Base context is zip: " + this .context);
299: }
300:
301: org.apache.excalibur.source.Source source = null;
302: try {
303: source = this .sourceResolver.resolveURI(this .context
304: + newContext);
305: this .context = source.getURI();
306: } finally {
307: this .sourceResolver.release(source);
308: }
309: } else if (newContext.length() > 0) {
310: String sContext;
311: // if we got a absolute context or one with a protocol resolve it
312: if (newContext.charAt(0) == '/') {
313: // context starts with the '/' - absolute file URL
314: sContext = "file:" + newContext;
315: } else if (newContext.indexOf(':') > 1) {
316: // context have ':' - absolute URL
317: sContext = newContext;
318: } else {
319: // context is relative to old one
320: sContext = this .context + '/' + newContext;
321: }
322:
323: // Cut the file name part from context (if present)
324: int i = sContext.lastIndexOf('/');
325: if (i != -1 && i + 1 < sContext.length()) {
326: sContext = sContext.substring(0, i + 1);
327: }
328:
329: org.apache.excalibur.source.Source source = null;
330: try {
331: source = this .sourceResolver.resolveURI(sContext);
332: this .context = source.getURI();
333: } finally {
334: this .sourceResolver.release(source);
335: }
336: }
337:
338: if (getLogger().isDebugEnabled()) {
339: getLogger().debug("New context is " + this .context);
340: }
341: }
342:
343: public void globalRedirect(boolean sessionmode, String newURL)
344: throws IOException {
345: redirect(sessionmode, newURL);
346: }
347:
348: // Request methods
349:
350: /**
351: * Returns the request view
352: */
353: public String getView() {
354: return this .view;
355: }
356:
357: /**
358: * Returns the request action
359: */
360: public String getAction() {
361: return this .action;
362: }
363:
364: // Response methods
365:
366: /**
367: * Set a status code
368: */
369: public void setStatus(int statusCode) {
370: }
371:
372: // Object model method
373:
374: /**
375: * Returns a Map containing environment specific objects
376: */
377: public Map getObjectModel() {
378: return this .objectModel;
379: }
380:
381: /**
382: * Resolve an entity.
383: * @deprecated Use the resolveURI methods instead
384: */
385: public Source resolve(String systemId) throws ProcessingException,
386: SAXException, IOException {
387: Deprecation.logger
388: .warn("The method SourceResolver.resolve(String) is "
389: + "deprecated. Use resolveURI(String) instead.");
390: if (!this .initializedComponents) {
391: initComponents();
392: }
393:
394: if (getLogger().isDebugEnabled()) {
395: getLogger().debug(
396: "Resolving '" + systemId + "' in context '"
397: + this .context + "'");
398: }
399:
400: if (systemId == null) {
401: throw new SAXException("Invalid System ID");
402: }
403:
404: // get the wrapper class - we don't want to import the wrapper directly
405: // to avoid a direct dependency from the core to the deprecation package
406: Class clazz;
407: try {
408: clazz = ClassUtils
409: .loadClass("org.apache.cocoon.components.source.impl.AvalonToCocoonSourceInvocationHandler");
410: } catch (Exception e) {
411: throw new ProcessingException(
412: "The deprecated resolve() method of the environment was called."
413: + "Please either update your code to use the new resolveURI() method or"
414: + " install the deprecation support.", e);
415: }
416:
417: if (null == avalonToCocoonSourceWrapper) {
418: synchronized (getClass()) {
419: try {
420: avalonToCocoonSourceWrapper = clazz
421: .getDeclaredMethod(
422: "createProxy",
423: new Class[] {
424: ClassUtils
425: .loadClass("org.apache.excalibur.source.Source"),
426: ClassUtils
427: .loadClass("org.apache.excalibur.source.SourceResolver"),
428: ClassUtils
429: .loadClass(Environment.class
430: .getName()),
431: ClassUtils
432: .loadClass(ComponentManager.class
433: .getName()) });
434: } catch (Exception e) {
435: throw new ProcessingException(
436: "The deprecated resolve() method of the environment was called."
437: + "Please either update your code to use the new resolveURI() method or"
438: + " install the deprecation support.",
439: e);
440: }
441: }
442: }
443:
444: try {
445: org.apache.excalibur.source.Source source = resolveURI(systemId);
446: Source wrappedSource = (Source) avalonToCocoonSourceWrapper
447: .invoke(clazz, new Object[] { source,
448: this .sourceResolver, this , this .manager });
449: return wrappedSource;
450: } catch (SourceException se) {
451: throw SourceUtil.handle(se);
452: } catch (Exception e) {
453: throw new ProcessingException(
454: "Unable to create source wrapper.", e);
455: }
456: }
457:
458: /**
459: * Check if the response has been modified since the same
460: * "resource" was requested.
461: * The caller has to test if it is really the same "resource"
462: * which is requested.
463: * @return true if the response is modified or if the
464: * environment is not able to test it
465: */
466: public boolean isResponseModified(long lastModified) {
467: return true; // always modified
468: }
469:
470: /**
471: * Mark the response as not modified.
472: */
473: public void setResponseIsNotModified() {
474: // does nothing
475: }
476:
477: public Object getAttribute(String name) {
478: return this .attributes.get(name);
479: }
480:
481: public void setAttribute(String name, Object value) {
482: this .attributes.put(name, value);
483: }
484:
485: protected boolean hasAttribute(String name) {
486: return this .attributes.containsKey(name);
487: }
488:
489: public void removeAttribute(String name) {
490: this .attributes.remove(name);
491: }
492:
493: public Enumeration getAttributeNames() {
494: return new IteratorEnumeration(this .attributes.keySet()
495: .iterator());
496: }
497:
498: /**
499: * Get the output stream where to write the generated resource.
500: * @deprecated Use {@link #getOutputStream(int)} instead.
501: */
502: public OutputStream getOutputStream() throws IOException {
503: Deprecation.logger
504: .warn("The method Environment.getOutputStream() "
505: + "is deprecated. Use getOutputStream(-1) instead.");
506: // by default we use the complete buffering output stream
507: return getOutputStream(-1);
508: }
509:
510: /**
511: * Get the output stream where to write the generated resource.
512: * The returned stream is buffered by the environment. If the
513: * buffer size is -1 then the complete output is buffered.
514: * If the buffer size is 0, no buffering takes place.
515: *
516: * <br>This method replaces {@link #getOutputStream()}.
517: */
518: public OutputStream getOutputStream(int bufferSize)
519: throws IOException {
520:
521: // This method could be called several times during request processing
522: // with differing values of bufferSize and should handle this situation
523: // correctly.
524:
525: if (bufferSize == -1) {
526: if (this .secureOutputStream == null) {
527: this .secureOutputStream = new BufferedOutputStream(
528: this .outputStream);
529: }
530: return this .secureOutputStream;
531: } else if (bufferSize == 0) {
532: // Discard secure output stream if it was created before.
533: if (this .secureOutputStream != null) {
534: this .secureOutputStream = null;
535: }
536: return this .outputStream;
537: } else {
538: // FIXME Triple buffering, anyone?
539: this .outputStream = new java.io.BufferedOutputStream(
540: this .outputStream, bufferSize);
541: return this .outputStream;
542: }
543: }
544:
545: /**
546: * Reset the response if possible. This allows error handlers to have
547: * a higher chance to produce clean output if the pipeline that raised
548: * the error has already output some data.
549: *
550: * @return true if the response was successfully reset
551: */
552: public boolean tryResetResponse() throws IOException {
553: if (this .secureOutputStream != null) {
554: this .secureOutputStream.clearBuffer();
555: return true;
556: }
557: return false;
558: }
559:
560: /**
561: * Commit the response
562: */
563: public void commitResponse() throws IOException {
564: if (this .secureOutputStream != null) {
565: this .setContentLength(this .secureOutputStream.getCount());
566: this .secureOutputStream.realFlush();
567: } else if (this .outputStream != null) {
568: this .outputStream.flush();
569: }
570: }
571:
572: /**
573: * Get a <code>Source</code> object.
574: */
575: public org.apache.excalibur.source.Source resolveURI(
576: final String location) throws MalformedURLException,
577: IOException, SourceException {
578: return this .resolveURI(location, null, null);
579: }
580:
581: /**
582: * Get a <code>Source</code> object.
583: */
584: public org.apache.excalibur.source.Source resolveURI(
585: final String location, String baseURI, final Map parameters)
586: throws MalformedURLException, IOException, SourceException {
587: if (!this .initializedComponents) {
588: this .initComponents();
589: }
590: return this .sourceResolver.resolveURI(location, baseURI,
591: parameters);
592: }
593:
594: /**
595: * Releases a resolved resource
596: */
597: public void release(final org.apache.excalibur.source.Source source) {
598: if (null != source) {
599: this .sourceResolver.release(source);
600: }
601: }
602:
603: /**
604: * Initialize the components for the environment
605: * This gets the source resolver and the xmlizer component
606: */
607: protected void initComponents() {
608: this .initializedComponents = true;
609: try {
610: this .manager = CocoonComponentManager
611: .getSitemapComponentManager();
612: this .sourceResolver = (org.apache.excalibur.source.SourceResolver) this .manager
613: .lookup(org.apache.excalibur.source.SourceResolver.ROLE);
614: if (this .tempInitContext != null) {
615: org.apache.excalibur.source.Source source = null;
616: try {
617: source = this .sourceResolver
618: .resolveURI(this .tempInitContext);
619: this .context = source.getURI();
620:
621: if (this .rootContext == null) // hack for EnvironmentWrapper
622: this .rootContext = this .context;
623: } finally {
624: this .sourceResolver.release(source);
625: }
626: this .tempInitContext = null;
627: }
628: } catch (ComponentException ce) {
629: // this should never happen!
630: throw new CascadingRuntimeException(
631: "Unable to lookup component.", ce);
632: } catch (IOException ie) {
633: throw new CascadingRuntimeException(
634: "Unable to resolve URI: " + this .tempInitContext,
635: ie);
636: }
637: }
638:
639: /**
640: * Notify that the processing starts.
641: */
642: public void startingProcessing() {
643: // do nothing here
644: }
645:
646: /**
647: * Notify that the processing is finished
648: * This can be used to cleanup the environment object
649: */
650: public void finishingProcessing() {
651: if (null != this .manager) {
652: this .manager.release(this .sourceResolver);
653: this .manager = null;
654: this .sourceResolver = null;
655: }
656: this .initializedComponents = false;
657: }
658:
659: /* (non-Javadoc)
660: * @see org.apache.cocoon.environment.Environment#isInternRedirect()
661: */
662: public boolean isInternalRedirect() {
663: return false;
664: }
665: }
|