blob: 682298d8ed001741e954b78198a69140d0521ce8 [file] [log] [blame]
/*
* 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.empire.jsf2.app;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import javax.faces.FactoryFinder;
import javax.faces.application.Application;
import javax.faces.application.ApplicationFactory;
import javax.faces.application.FacesMessage;
import javax.faces.application.FacesMessage.Severity;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponent;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.sql.DataSource;
import org.apache.empire.commons.StringUtils;
import org.apache.empire.data.DataType;
import org.apache.empire.db.DBDatabase;
import org.apache.empire.exceptions.InternalException;
import org.apache.empire.exceptions.InvalidArgumentException;
import org.apache.empire.exceptions.NotSupportedException;
import org.apache.empire.jsf2.controls.TextAreaInputControl;
import org.apache.empire.jsf2.controls.TextInputControl;
import org.apache.empire.jsf2.impl.FacesImplementation;
import org.apache.empire.jsf2.impl.ResourceTextResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class WebApplication
{
private static final Logger log = LoggerFactory.getLogger(WebApplication.class);
private static final String CONNECTION_ATTRIBUTE = "dbConnections";
public static String APPLICATION_BEAN_NAME = "webApplication";
protected TextResolver[] textResolvers = null;
private String webRoot = null;
private FacesImplementation facesImpl = null;
private static WebApplication appInstance = null;
public static WebApplication getInstance()
{
if (appInstance==null)
log.warn("No WebApplication instance available. Please add a PostConstructApplicationEvent using WebAppStartupListener in your faces-config.xml to create the WebApplication object.");
// return instance
return appInstance;
}
protected abstract void init(ServletContext servletContext);
protected abstract DataSource getAppDataSource(DBDatabase db);
protected WebApplication()
{ // subscribe
log.info("WebApplication {} created", getClass().getName());
// Must be a singleton
if (appInstance!=null) {
throw new RuntimeException("An attempt was made to create second instance of WebApplication. WebApplication must be a singleton!");
}
// set Instance
appInstance = this;
}
/**
* Init the Application
* @param servletContext
*/
public final void init(FacesImplementation facesImpl, FacesContext startupContext)
{
// Only call once!
if (this.facesImpl!=null || this.webRoot!=null)
{ // already initialized
log.warn("WARNING: WebApplication has already been initialized! Continuing without init...");
return;
}
// set imppl
this.facesImpl = facesImpl;
// webRoot
ServletContext servletContext = (ServletContext) startupContext.getExternalContext().getContext();
webRoot = servletContext.getContextPath();
servletContext.setAttribute("webRoot", webRoot);
servletContext.setAttribute("app", this);
// Init
init(servletContext);
// text resolvers
log.info("*** initTextResolvers() ***");
ApplicationFactory appFactory = (ApplicationFactory) FactoryFinder.getFactory(FactoryFinder.APPLICATION_FACTORY);
Application app = appFactory.getApplication();
initTextResolvers(app);
// Log info
log.info("*** WebApplication initialization complete ***");
log.info("JSF-Implementation is '{}'", facesImpl.getClass().getName());
log.info("WebRoot is '{}'", webRoot);
}
public void destroy()
{
// Override if needed
}
/* Context handling */
/**
* handle request cleanup
* @param ctx
*/
public void onRequestComplete(final FacesContext ctx)
{
releaseAllConnections(ctx);
}
/**
* handle view not found
* @param fc
* @param HttpServletRequest
*/
public void onViewNotFound(final FacesContext fc, final HttpServletRequest req)
{ // View not Found Error
log.warn("No view found for request to '{}'. Use FacesUtils.redirectDirectly() to redirect to valid view.", req.getRequestURI());
}
/**
* handle view change
* @param fc
* @param viewId
*/
public void onChangeView(final FacesContext fc, String viewId)
{ // allow custom view change logic
// clear page resources
Map<String, Object> sm = FacesUtils.getSessionMap(fc);
if (sm!=null)
sm.remove(FacesUtils.PAGE_RESOURCE_MAP_ATTRIBUTE);
}
public void addJavascriptCall(final FacesContext fc, String function)
{
throw new NotSupportedException(this, "addJavascriptCall");
}
/**
* return the interface for Implementation specific features
* that are specific for Mojarra or MyFaces
*/
public FacesImplementation getFacesImplementation()
{
return facesImpl;
}
/**
* returns the web context path as returned from ServletContext.getContextPath()
*/
public String getWebRoot()
{
return webRoot;
}
/**
* returns the active locale for a given FacesContext
*/
public Locale getContextLocale(final FacesContext ctx)
{
UIViewRoot root;
// Start out with the default locale
Locale locale;
Locale defaultLocale = Locale.getDefault();
locale = defaultLocale;
// See if this FacesContext has a ViewRoot
if (null != (root = ctx.getViewRoot()))
{
// If so, ask it for its Locale
if (null == (locale = root.getLocale()))
{
// If the ViewRoot has no Locale, fall back to the default.
locale = defaultLocale;
}
}
return locale;
}
public TextResolver getTextResolver(Locale locale)
{
// No text Resolvers provided
if (textResolvers == null || textResolvers.length == 0)
{
throw new NotSupportedException(this, "getTextResolver");
}
// Lookup resolver for locale
for (int i = 0; i < textResolvers.length; i++)
if (locale.equals(textResolvers[i].getLocale()))
return textResolvers[i];
// locale not found: return default
return textResolvers[0];
}
public TextResolver getTextResolver(FacesContext ctx)
{
return getTextResolver(getContextLocale(ctx));
}
/**
* checks if the current context contains an error
* @param fc the FacesContext
* @return true if the context has an error set or false otherwise
*/
public boolean hasError(final FacesContext fc)
{
Iterator<FacesMessage> msgIterator = fc.getMessages();
if (msgIterator != null)
{ // Check Messages
while (msgIterator.hasNext())
{ // Check Severity
Severity fms = msgIterator.next().getSeverity();
if (fms == FacesMessage.SEVERITY_ERROR || fms == FacesMessage.SEVERITY_FATAL)
return true;
}
}
return false;
}
/**
* returns true if a form input element has been partially submitted
* @param fc the Faces Context
* @return the componentId or null if no partial submit was been performed
*/
public boolean isPartialSubmit(final FacesContext fc)
{
// Override for your JSF component Framework. e.g. for IceFaces
// Map<String,String> parameterMap = fc.getExternalContext().getRequestParameterMap();
// return ObjectUtils.getBoolean(parameterMap.get("ice.submit.partial"));
return false;
}
/**
* returns the componentId for which a partial submit has been performed.
* @param fc the Faces Context
* @return the componentId or null if no partial submit was been performed
*/
public String getPartialSubmitComponentId(final FacesContext fc)
{
// Override for your JSF component Framework. e.g. for IceFaces
// Map<String,String> parameterMap = fc.getExternalContext().getRequestParameterMap();
// return parameterMap.get("ice.event.captured");
return null;
}
/**
* finds the component with the given id that is located in the same NamingContainer as a given component
* @param fc the FacesContext
* @param componentId the component id
* @param nearComponent a component within the same naming container from which to start the search (optional)
* @return the component or null if no component was found
*/
public UIComponent findComponent(FacesContext fc, String componentId, UIComponent nearComponent)
{
if (StringUtils.isEmpty(componentId))
throw new InvalidArgumentException("componentId", componentId);
// Begin search near given component (if any)
UIComponent component = null;
if (nearComponent != null)
{ // Search below the nearest naming container
component = nearComponent.findComponent(componentId);
if (component == null)
{ // Recurse upwards
UIComponent nextParent = nearComponent;
while (true)
{
nextParent = nextParent.getParent();
// search NamingContainers only
while (nextParent != null && !(nextParent instanceof NamingContainer))
{
nextParent = nextParent.getParent();
}
if (nextParent == null)
{
break;
}
else
{
component = nextParent.findComponent(componentId);
}
if (component != null)
{
break;
}
}
}
}
// Not found. Search the entire tree
if (component == null)
component = findChildComponent(fc.getViewRoot(), componentId);
// done
return component;
}
/**
* finds a child component with the given id that is located below the given parent component
* @param parent the parent
* @param componentId the component id
* @return the component or null if no component was found
*/
public static UIComponent findChildComponent(UIComponent parent, String componentId)
{
UIComponent component = null;
if (parent.getChildCount() == 0)
return null;
Iterator<UIComponent> children = parent.getChildren().iterator();
while (children.hasNext())
{
UIComponent nextChild = children.next();
if (nextChild instanceof NamingContainer)
{
component = nextChild.findComponent(componentId);
}
if (component == null)
{
component = findChildComponent(nextChild, componentId);
}
if (component != null)
{
break;
}
}
return component;
}
/**
* returns the default input control type for a given data Type
* @see org.apache.empire.jsf2.controls.InputControlManager
* @param dataType
* @return an Input Cnotrol type
*/
public String getDefaultControlType(DataType dataType)
{
switch (dataType)
{
case CLOB:
return TextAreaInputControl.NAME;
default:
return TextInputControl.NAME;
}
}
/* Message handling */
protected void initTextResolvers(Application app)
{
int count = 0;
Iterator<Locale> locales = app.getSupportedLocales();
for (count = 0; locales.hasNext(); count++)
{
locales.next();
}
// get message bundles
String messageBundle = app.getMessageBundle();
textResolvers = new TextResolver[count];
locales = app.getSupportedLocales();
for (int i = 0; locales.hasNext(); i++)
{
Locale locale = locales.next();
textResolvers[i] = new ResourceTextResolver(ResourceBundle.getBundle(messageBundle, locale));
log.info("added TextResolver for {} bundle='{}'", locale.getLanguage(), messageBundle);
}
}
/**
* returns a connection from the connection pool
*
* @return
*/
protected Connection getConnection(DBDatabase db)
{
// Get From Pool
try
{ // Obtain a connection
Connection conn = getAppDataSource(db).getConnection();
conn.setAutoCommit(false);
return conn;
}
catch (SQLException e)
{
log.error("Failed to get connection from pool.", e);
throw new InternalException(e);
}
}
/**
* releases a connection from the connection pool
*/
protected void releaseConnection(DBDatabase db, Connection conn, boolean commit)
{
try
{ // release connection
if (conn == null)
{
return;
}
// Commit or rollback connection depending on the exit code
if (commit)
{ // success: commit all changes
db.commit(conn);
log.debug("REQUEST commited.");
}
else
{ // failure: rollback all changes
db.rollback(conn);
log.debug("REQUEST rolled back.");
}
// Release Connection
conn.close();
// done
if (log.isDebugEnabled())
log.debug("REQUEST returned connection to pool.");
}
catch (SQLException e)
{
log.error("Error releasing connection", e);
e.printStackTrace();
}
}
/**
* returns a connection for the current Request
*/
public Connection getConnectionForRequest(FacesContext fc, DBDatabase db)
{
if (fc == null)
throw new InvalidArgumentException("FacesContext", fc);
if (db == null)
throw new InvalidArgumentException("DBDatabase", db);
// Get Conneciton map
@SuppressWarnings("unchecked")
Map<DBDatabase, Connection> connMap = (Map<DBDatabase, Connection>) FacesUtils.getRequestAttribute(fc, CONNECTION_ATTRIBUTE);
if (connMap != null && connMap.containsKey(db))
return connMap.get(db);
// Pooled Connection
Connection conn = getConnection(db);
if (conn == null)
return null;
// Add to map
if (connMap == null)
{
connMap = new HashMap<DBDatabase, Connection>();
FacesUtils.setRequestAttribute(fc, CONNECTION_ATTRIBUTE, connMap);
}
connMap.put(db, conn);
return conn;
}
/**
* releases the current request connection
* @param fc the FacesContext
* @param commit when true changes are committed otherwise they are rolled back
*/
public void releaseAllConnections(final FacesContext fc, boolean commit)
{
@SuppressWarnings("unchecked")
Map<DBDatabase, Connection> connMap = (Map<DBDatabase, Connection>) FacesUtils.getRequestAttribute(fc, CONNECTION_ATTRIBUTE);
if (connMap != null)
{ // Walk the connection map
for (Map.Entry<DBDatabase, Connection> e : connMap.entrySet())
{
releaseConnection(e.getKey(), e.getValue(), commit);
}
// remove from request map
FacesUtils.setRequestAttribute(fc, CONNECTION_ATTRIBUTE, null);
}
}
public void releaseAllConnections(final FacesContext fc)
{
releaseAllConnections(fc, !hasError(fc));
}
public void releaseConnection(final FacesContext fc, DBDatabase db, boolean commit)
{
@SuppressWarnings("unchecked")
Map<DBDatabase, Connection> connMap = (Map<DBDatabase, Connection>) FacesUtils.getRequestAttribute(fc, CONNECTION_ATTRIBUTE);
if (connMap != null && connMap.containsKey(db))
{ // Walk the connection map
releaseConnection(db, connMap.get(db), commit);
connMap.remove(db);
if (connMap.size() == 0)
FacesUtils.setRequestAttribute(fc, CONNECTION_ATTRIBUTE, null);
}
}
public void releaseConnection(final FacesContext fc, DBDatabase db)
{
releaseConnection(fc, db, !hasError(fc));
}
}