| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache 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.apache.org/licenses/LICENSE-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.apache.cocoon.components.treeprocessor.sitemap; |
| |
| import org.apache.avalon.framework.component.ComponentException; |
| import org.apache.avalon.framework.component.ComponentManager; |
| import org.apache.avalon.framework.component.Composable; |
| import org.apache.avalon.framework.logger.AbstractLogEnabled; |
| import org.apache.avalon.framework.logger.Logger; |
| |
| import org.apache.cocoon.Constants; |
| import org.apache.cocoon.ResourceNotFoundException; |
| import org.apache.cocoon.components.notification.Notifying; |
| import org.apache.cocoon.components.notification.NotifyingBuilder; |
| import org.apache.cocoon.components.pipeline.ProcessingPipeline; |
| import org.apache.cocoon.components.treeprocessor.InvokeContext; |
| import org.apache.cocoon.components.treeprocessor.ProcessingNode; |
| import org.apache.cocoon.environment.Environment; |
| import org.apache.cocoon.environment.ObjectModelHelper; |
| |
| import java.io.IOException; |
| import java.util.Map; |
| |
| /** |
| * Helps to call error handlers from PipelineNode and PipelinesNode. |
| * |
| * @author <a href="mailto:juergen.seitz@basf-it-services.com">Jürgen Seitz</a> |
| * @author <a href="mailto:bluetkemeier@s-und-n.de">Björn Lütkemeier</a> |
| * @version $Id$ |
| */ |
| public class ErrorHandlerHelper extends AbstractLogEnabled |
| implements Composable { |
| |
| private ComponentManager manager; |
| |
| /** |
| * Logger for handled errors |
| */ |
| protected Logger handledErrorsLogger; |
| |
| /** |
| * Error handling node for the ResourceNotFoundException |
| * (deprecated) |
| */ |
| private HandleErrorsNode error404; |
| |
| /** |
| * Error handling node for all other exceptions |
| */ |
| private HandleErrorsNode error500; |
| |
| public void enableLogging(Logger logger) { |
| super.enableLogging(logger); |
| this.handledErrorsLogger = logger.getChildLogger("handled-errors"); |
| } |
| |
| /** |
| * The component manager is used to create notifying builders. |
| */ |
| public void compose(ComponentManager manager) { |
| this.manager = manager; |
| } |
| |
| void set404Handler(ProcessingNode node) { |
| this.error404 = (HandleErrorsNode) node; |
| } |
| |
| void set500Handler(ProcessingNode node) { |
| this.error500 = (HandleErrorsNode) node; |
| } |
| |
| /** |
| * @return true if has no error handler nodes set |
| */ |
| public boolean isEmpty() { |
| return this.error404 == null && this.error500 == null; |
| } |
| |
| public boolean isInternal() { |
| return this.error500 != null && this.error500.isInternal(); |
| } |
| |
| public boolean isExternal() { |
| return this.error500 != null && this.error500.isExternal(); |
| } |
| |
| /** |
| * Handle error. |
| */ |
| public boolean invokeErrorHandler(Exception ex, |
| Environment env, |
| InvokeContext context) |
| throws Exception { |
| return prepareErrorHandler(ex, env, context) != null; |
| } |
| |
| /** |
| * Prepare error handler for the internal pipeline error handling. |
| * |
| * <p>If building pipeline only, error handling pipeline will be |
| * built and returned. If building and executing pipeline, |
| * error handling pipeline will be built and executed.</p> |
| */ |
| public ProcessingPipeline prepareErrorHandler(Exception ex, |
| Environment env, |
| InvokeContext context) |
| throws Exception { |
| boolean internal = !env.isExternal() && !env.isInternalRedirect(); |
| |
| if (internal && !isInternal()) { |
| // Propagate exception on internal request: No internal handler. |
| throw ex; |
| } else if (!internal && !isExternal()) { |
| // Propagate exception on external request: No external handler. |
| throw ex; |
| } else if (!internal && error404 != null && ex instanceof ResourceNotFoundException) { |
| // Invoke 404-specific handler: Only on external requests. Deprecated. |
| return prepareErrorHandler(error404, ex, env, context); |
| } else if (error500 != null) { |
| // Invoke global handler |
| return prepareErrorHandler(error500, ex, env, context); |
| } |
| |
| // Exception was not handled in this error handler, propagate. |
| throw ex; |
| } |
| |
| /** |
| * Handle error using specified error handler processing node. |
| */ |
| public boolean invokeErrorHandler(ProcessingNode node, |
| Exception ex, |
| Environment env, |
| InvokeContext context) |
| throws Exception { |
| return prepareErrorHandler(node, ex, env, context) != null; |
| } |
| |
| /** |
| * Prepare (or execute) error handler using specified error handler |
| * processing node. |
| * |
| * <p>If building pipeline only, error handling pipeline will be |
| * built and returned. If building and executing pipeline, |
| * error handling pipeline will be built and executed.</p> |
| */ |
| private ProcessingPipeline prepareErrorHandler(ProcessingNode node, |
| Exception ex, |
| Environment env, |
| InvokeContext context) |
| throws Exception { |
| if (ex instanceof ResourceNotFoundException) { |
| this.handledErrorsLogger.error(ex.getMessage()); |
| } else { |
| this.handledErrorsLogger.error(ex.getMessage(), ex); |
| } |
| |
| try { |
| prepare(context, env, ex); |
| |
| // Create error context |
| InvokeContext errorContext = new InvokeContext(context.isBuildingPipelineOnly()); |
| errorContext.enableLogging(getLogger()); |
| errorContext.setRedirector(context.getRedirector()); |
| errorContext.compose(this.manager); |
| try { |
| // Process error handling node |
| if (node.invoke(env, errorContext)) { |
| // Exception was handled. |
| return errorContext.getProcessingPipeline(); |
| } |
| } finally { |
| errorContext.dispose(); |
| } |
| } catch (Exception e) { |
| getLogger().error("An exception occured while handling errors at " + node.getLocation(), e); |
| // Rethrow it: It will either be handled by the parent sitemap or by the environment (e.g. Cocoon servlet) |
| throw e; |
| } |
| |
| // Exception was not handled in this error handler, propagate. |
| throw ex; |
| } |
| |
| /** |
| * Build notifying object |
| */ |
| private void prepare(InvokeContext context, Environment env, Exception ex) |
| throws IOException, ComponentException { |
| Map objectModel = env.getObjectModel(); |
| if (objectModel.get(Constants.NOTIFYING_OBJECT) == null) { |
| // error has not been processed by another handler before |
| |
| // Try to reset the response to avoid mixing already produced output |
| // and error page. |
| if (!context.isBuildingPipelineOnly()) { |
| env.tryResetResponse(); |
| } |
| |
| // Create a Notifying |
| NotifyingBuilder notifyingBuilder = (NotifyingBuilder) this.manager.lookup(NotifyingBuilder.ROLE); |
| Notifying currentNotifying = null; |
| try { |
| currentNotifying = notifyingBuilder.build(this, ex); |
| } finally { |
| this.manager.release(notifyingBuilder); |
| } |
| |
| // Add it to the object model |
| objectModel.put(Constants.NOTIFYING_OBJECT, currentNotifying); |
| |
| // Also add the exception |
| objectModel.put(ObjectModelHelper.THROWABLE_OBJECT, ex); |
| } |
| } |
| } |