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