blob: 48dd8a3fe6ee66093a223a5f7906ed67dd7dc71b [file] [log] [blame]
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Apache TomEE</title>
<meta name="description"
content="Apache TomEE is a lightweight, yet powerful, JavaEE Application server with feature rich tooling." />
<meta name="keywords" content="tomee,asf,apache,javaee,jee,shade,embedded,test,junit,applicationcomposer,maven,arquillian" />
<meta name="author" content="Luka Cvetinovic for Codrops" />
<link rel="icon" href="../../../favicon.ico">
<link rel="icon" type="image/png" href="../../../favicon.png">
<meta name="msapplication-TileColor" content="#80287a">
<meta name="theme-color" content="#80287a">
<link rel="stylesheet" type="text/css" href="../../../css/normalize.css">
<link rel="stylesheet" type="text/css" href="../../../css/bootstrap.css">
<link rel="stylesheet" type="text/css" href="../../../css/owl.css">
<link rel="stylesheet" type="text/css" href="../../../css/animate.css">
<link rel="stylesheet" type="text/css" href="../../../fonts/font-awesome-4.1.0/css/font-awesome.min.css">
<link rel="stylesheet" type="text/css" href="../../../fonts/eleganticons/et-icons.css">
<link rel="stylesheet" type="text/css" href="../../../css/jqtree.css">
<link rel="stylesheet" type="text/css" href="../../../css/idea.css">
<link rel="stylesheet" type="text/css" href="../../../css/cardio.css">
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-2717626-1']);
_gaq.push(['_setDomainName', 'apache.org']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</head>
<body>
<div class="preloader">
<img src="../../../img/loader.gif" alt="Preloader image">
</div>
<nav class="navbar">
<div class="container">
<div class="row"> <div class="col-md-12">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">
<span>
<img src="../../../img/logo-active.png">
</span>
Apache TomEE
</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav navbar-right main-nav">
<li><a href="../../../docs.html">Documentation</a></li>
<li><a href="../../../community/index.html">Community</a></li>
<li><a href="../../../security/security.html">Security</a></li>
<li><a href="../../../download-ng.html">Downloads</a></li>
</ul>
</div>
<!-- /.navbar-collapse -->
</div></div>
</div>
<!-- /.container-fluid -->
</nav>
<div id="main-block" class="container main-block">
<div class="row title">
<div class="col-md-12">
<div class='page-header'>
<h1>Roteamento dinâmico de fonte de dados</h1>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div id="preamble">
<div class="sectionbody">
<div class="paragraph">
<p>A API de fonte de dados dinâmica do TomEE visa permitir o uso de várias fontes de dados como se fosse uma do ponto de vista do aplicativo.</p>
</div>
<div class="paragraph">
<p>Pode ser útil por razões técnicas (balanceamento de carga, por exemplo) ou
razões geralmente mais funcionais (filtragem, agregação, enriquecimento &#8230;&#8203;).
No entanto, observe que você pode escolher apenas uma fonte de dados por transação.
Isso significa que o objetivo desse recurso não é alternar mais de uma vez
fonte de dados em uma transação. O código a seguir não funcionará:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java">@Stateless
public class MyEJB {
@Resource private MyRouter router;
@PersistenceContext private EntityManager em;
public void workWithDataSources() {
router.setDataSource("ds1");
em.persist(new MyEntity());
router.setDataSource("ds2"); // mesma transação -&gt; esta invocação não funciona
em.persist(new MyEntity());
}
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>Neste exemplo, a implementação simplesmente usa uma fonte de dados de seu nome
e precisa ser definido antes de usar qualquer operação JPA na transação
(para manter a lógica simples no exemplo).</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_a_implementação_do_roteador">A implementação do roteador</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Nosso roteador possui dois parâmetros de configuração: <strong>uma lista de nomes jndi
representando fontes de dados para usar</strong> uma fonte de dados padrão para usar</p>
</div>
<div class="sect2">
<h3 id="_implementação_de_roteador">Implementação de roteador</h3>
<div class="paragraph">
<p>A interface Router (<code>org.apache.openejb.resource.jdbc.Router</code>) tem
somente um método para implementar, <code>public DataSource getDataSource()</code></p>
</div>
<div class="paragraph">
<p>Nossa implementação <code>DeterminedRouter</code> usa um ThreadLocal para gerenciar a
fonte de dados usada atualmente. Lembre-se de que a JPA usou mais de uma vez o
Método getDatasource() para uma operação. Alterar a fonte de dados em
uma transação é perigosa e deve ser evitada.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java">package org.superbiz.dynamicdatasourcerouting;
import org.apache.openejb.resource.jdbc.AbstractRouter;
import javax.naming.NamingException;
import javax.sql.DataSource;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class DeterminedRouter extends AbstractRouter {
private String dataSourceNames;
private String defaultDataSourceName;
private Map&lt;String, DataSource&gt; dataSources = null;
private ThreadLocal&lt;DataSource&gt; currentDataSource = new ThreadLocal&lt;DataSource&gt;();
/**
* @param datasourceList nome do recurso de origem de dados, separador é um espaço
*/
public void setDataSourceNames(String datasourceList) {
dataSourceNames = datasourceList;
}
/**
* fonte de dados de pesquisa em recursos openejb
*/
private void init() {
dataSources = new ConcurrentHashMap&lt;String, DataSource&gt;();
for (String ds : dataSourceNames.split(" ")) {
try {
Object o = getOpenEJBResource(ds);
if (o instanceof DataSource) {
dataSources.put(ds, DataSource.class.cast(o));
}
} catch (NamingException e) {
// ignorado
}
}
}
/**
* @return o usuário selecionou a fonte de dados, se estiver definida
      * ou o padrão
* @throws IllegalArgumentException se a fonte de dados não for encontrada
*/
@Override
public DataSource getDataSource() {
// lazy init of routed datasources
if (dataSources == null) {
init();
}
// se nenhuma fonte de dados estiver selecionada, use a fonte padrão
if (currentDataSource.get() == null) {
if (dataSources.containsKey(defaultDataSourceName)) {
return dataSources.get(defaultDataSourceName);
} else {
throw new IllegalArgumentException("você precisa especificar pelo menos uma fonte de dados");
}
}
// o desenvolvedor configurou a fonte de dados para usar
return currentDataSource.get();
}
/**
*
* @param datasourceName nome da fonte de dados
*/
public void setDataSource(String datasourceName) {
if (dataSources == null) {
init();
}
if (!dataSources.containsKey(datasourceName)) {
throw new IllegalArgumentException("data source called " + datasourceName + " can't be found.");
}
DataSource ds = dataSources.get(datasourceName);
currentDataSource.set(ds);
}
/**
* redefinir a fonte de dados
*/
public void clear() {
currentDataSource.remove();
}
public void setDefaultDataSourceName(String name) {
this.defaultDataSourceName = name;
}
}</code></pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_declarando_a_implementação">Declarando a implementação</h3>
<div class="paragraph">
<p>Para poder usar seu roteador como um recurso, você precisa fornecer uma
configuração de serviço. Isso é feito em um arquivo que você pode encontrar em
META-INF/org.router/ e chamado service-jar.xml (para sua implementação
é claro que você pode alterar o nome do pacote).</p>
</div>
<div class="paragraph">
<p>Ele contém o seguinte código:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-xml" data-lang="xml">&lt;ServiceJar&gt;
&lt;ServiceProvider id="DeterminedRouter" &lt;!-- o nome que você deseja usar --&gt;
service="Resource"
type="org.apache.openejb.resource.jdbc.Router"
class-name="org.superbiz.dynamicdatasourcerouting.DeterminedRouter"&gt; &lt;!-- classe de implementação --&gt;
# os parametros
DataSourceNames
DefaultDataSourceName
&lt;/ServiceProvider&gt;
&lt;/ServiceJar&gt;</code></pre>
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_usando_o_roteador">Usando o roteador</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Aqui temos um bean sem estado <code>RoutedPersister</code> que usa nosso
<code>DeterminedRouter</code></p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java">package org.superbiz.dynamicdatasourcerouting;
import javax.annotation.Resource;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
@Stateless
public class RoutedPersister {
@PersistenceContext(unitName = "router")
private EntityManager em;
@Resource(name = "My Router", type = DeterminedRouter.class)
private DeterminedRouter router;
public void persist(int id, String name, String ds) {
router.setDataSource(ds);
em.persist(new Person(id, name));
}
}</code></pre>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_o_teste">O teste</h2>
<div class="sectionbody">
<div class="paragraph">
<p>No modo de teste e usando a configuração de estilo de propriedade, a seguinte
configuração é usada:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java">public class DynamicDataSourceTest {
@Test
public void route() throws Exception {
String[] databases = new String[]{"database1", "database2", "database3"};
Properties properties = new Properties();
properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, LocalInitialContextFactory.class.getName());
// Recursos
// fontes de dados
for (int i = 1; i &lt;= databases.length; i++) {
String dbName = databases[i - 1];
properties.setProperty(dbName, "new://Resource?type=DataSource");
dbName += ".";
properties.setProperty(dbName + "JdbcDriver", "org.hsqldb.jdbcDriver");
properties.setProperty(dbName + "JdbcUrl", "jdbc:hsqldb:mem:db" + i);
properties.setProperty(dbName + "UserName", "sa");
properties.setProperty(dbName + "Password", "");
properties.setProperty(dbName + "JtaManaged", "true");
}
// router
properties.setProperty("My Router", "new://Resource?provider=org.router:DeterminedRouter&amp;type=" + DeterminedRouter.class.getName());
properties.setProperty("My Router.DatasourceNames", "database1 database2 database3");
properties.setProperty("My Router.DefaultDataSourceName", "database1");
// routed datasource
properties.setProperty("Routed Datasource", "new://Resource?provider=RoutedDataSource&amp;type=" + Router.class.getName());
properties.setProperty("Routed Datasource.Router", "My Router");
Context ctx = EJBContainer.createEJBContainer(properties).getContext();
RoutedPersister ejb = (RoutedPersister) ctx.lookup("java:global/dynamic-datasource-routing/RoutedPersister");
for (int i = 0; i &lt; 18; i++) {
// persistir uma pessoa no banco de dados db -&gt; tipo de round robin manual
String name = "record " + i;
String db = databases[i % 3];
ejb.persist(i, name, db);
}
// afirmar o número de registros do banco de dados usando jdbc
for (int i = 1; i &lt;= databases.length; i++) {
Connection connection = DriverManager.getConnection("jdbc:hsqldb:mem:db" + i, "sa", "");
Statement st = connection.createStatement();
ResultSet rs = st.executeQuery("select count(*) from PERSON");
rs.next();
assertEquals(6, rs.getInt(1));
st.close();
connection.close();
}
ctx.close();
}
}</code></pre>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_configuração_via_openejb_xml">Configuração via openejb.xml</h2>
<div class="sectionbody">
<div class="paragraph">
<p>O testcase acima usa propriedades para configuração. O caminho idêntico para fazê-lo através do <code>conf/openejb.xml</code> é o seguinte:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-xml" data-lang="xml">&lt;!-- Roteador e fonte de dados --&gt;
&lt;Resource id="My Router" type="org.apache.openejb.router.test.DynamicDataSourceTest$DeterminedRouter" provider="org.routertest:DeterminedRouter"&gt;
DatasourceNames = database1 database2 database3
DefaultDataSourceName = database1
&lt;/Resource&gt;
&lt;Resource id="Routed Datasource" type="org.apache.openejb.resource.jdbc.Router" provider="RoutedDataSource"&gt;
Router = My Router
&lt;/Resource&gt;
&lt;!-- fontes de dados reais --&gt;
&lt;Resource id="database1" type="DataSource"&gt;
JdbcDriver = org.hsqldb.jdbcDriver
JdbcUrl = jdbc:hsqldb:mem:db1
UserName = sa
Password
JtaManaged = true
&lt;/Resource&gt;
&lt;Resource id="database2" type="DataSource"&gt;
JdbcDriver = org.hsqldb.jdbcDriver
JdbcUrl = jdbc:hsqldb:mem:db2
UserName = sa
Password
JtaManaged = true
&lt;/Resource&gt;
&lt;Resource id="database3" type="DataSource"&gt;
JdbcDriver = org.hsqldb.jdbcDriver
JdbcUrl = jdbc:hsqldb:mem:db3
UserName = sa
Password
JtaManaged = true
&lt;/Resource&gt;</code></pre>
</div>
</div>
<div class="sect2">
<h3 id="_algum_hack_para_o_openjpa">Algum hack para o OpenJPA</h3>
<div class="paragraph">
<p>Usar mais de uma fonte de dados atrás de um EntityManager significa que
bancos de dados já foram criados. Se não for esse o caso, o provedor JPA
precisa criar a fonte de dados no momento da inicialização.</p>
</div>
<div class="paragraph">
<p>Hibernate faz isso, se você declarar seus bancos de dados vão funcionar. Contudo
com o OpenJPA (o provedor JPA padrão para o OpenEJB), a criação é
preguiçosa e isso acontece apenas uma vez; quando você alterna o banco de dados, ele não vai funcionar mais.</p>
</div>
<div class="paragraph">
<p>É claro que o OpenEJB fornece os recursos @Singleton e @Startup do Java EE 6
e podemos fazer um bean apenas fazendo uma descoberta simples, mesmo que não exista
entidades, apenas para forçar a criação do banco de dados:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java">@Startup
@Singleton
public class BoostrapUtility {
// injetar todos os bancos de dados reais
@PersistenceContext(unitName = "db1")
private EntityManager em1;
@PersistenceContext(unitName = "db2")
private EntityManager em2;
@PersistenceContext(unitName = "db3")
private EntityManager em3;
// forçar a criação de banco de dados
@PostConstruct
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public void initDatabase() {
em1.find(Person.class, 0);
em2.find(Person.class, 0);
em3.find(Person.class, 0);
}
}</code></pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_usando_a_fonte_de_dados_roteada">Usando a fonte de dados roteada</h3>
<div class="paragraph">
<p>Agora você configurou a maneira como deseja rotear sua operação JPA,
registrou os recursos e você inicializou seus bancos de dados, você pode usar
e veja como é simples:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java">@Stateless
public class RoutedPersister {
// injeção da fonte de dados "em proxy"
@PersistenceContext(unitName = "router")
private EntityManager em;
// injeção do roteador, você precisa configurar o banco de dados
@Resource(name = "My Router", type = DeterminedRouter.class)
private DeterminedRouter router;
public void persist(int id, String name, String ds) {
router.setDataSource(ds); // configurando o banco de dados para a transação atual
em.persist(new Person(id, name)); // usará o banco de dados ds automaticamente
}
}</code></pre>
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_executando">Executando</h2>
<div class="sectionbody">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-console" data-lang="console">-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running org.superbiz.dynamicdatasourcerouting.DynamicDataSourceTest
Apache OpenEJB 4.0.0-beta-1 build: 20111002-04:06
http://tomee.apache.org/
INFO - openejb.home = /Users/dblevins/examples/dynamic-datasource-routing
INFO - openejb.base = /Users/dblevins/examples/dynamic-datasource-routing
INFO - Using 'javax.ejb.embeddable.EJBContainer=true'
INFO - Configuring Service(id=Default Security Service, type=SecurityService, provider-id=Default Security Service)
INFO - Configuring Service(id=Default Transaction Manager, type=TransactionManager, provider-id=Default Transaction Manager)
INFO - Configuring Service(id=My Router, type=Resource, provider-id=DeterminedRouter)
INFO - Configuring Service(id=database3, type=Resource, provider-id=Default JDBC Database)
INFO - Configuring Service(id=database2, type=Resource, provider-id=Default JDBC Database)
INFO - Configuring Service(id=Routed Datasource, type=Resource, provider-id=RoutedDataSource)
INFO - Configuring Service(id=database1, type=Resource, provider-id=Default JDBC Database)
INFO - Found EjbModule in classpath: /Users/dblevins/examples/dynamic-datasource-routing/target/classes
INFO - Beginning load: /Users/dblevins/examples/dynamic-datasource-routing/target/classes
INFO - Configuring enterprise application: /Users/dblevins/examples/dynamic-datasource-routing
WARN - Method 'lookup' is not available for 'javax.annotation.Resource'. Probably using an older Runtime.
INFO - Configuring Service(id=Default Singleton Container, type=Container, provider-id=Default Singleton Container)
INFO - Auto-creating a container for bean BoostrapUtility: Container(type=SINGLETON, id=Default Singleton Container)
INFO - Configuring Service(id=Default Stateless Container, type=Container, provider-id=Default Stateless Container)
INFO - Auto-creating a container for bean RoutedPersister: Container(type=STATELESS, id=Default Stateless Container)
INFO - Auto-linking resource-ref 'java:comp/env/My Router' in bean RoutedPersister to Resource(id=My Router)
INFO - Configuring Service(id=Default Managed Container, type=Container, provider-id=Default Managed Container)
INFO - Auto-creating a container for bean org.superbiz.dynamicdatasourcerouting.DynamicDataSourceTest: Container(type=MANAGED, id=Default Managed Container)
INFO - Configuring PersistenceUnit(name=router)
INFO - Configuring PersistenceUnit(name=db1)
INFO - Auto-creating a Resource with id 'database1NonJta' of type 'DataSource for 'db1'.
INFO - Configuring Service(id=database1NonJta, type=Resource, provider-id=database1)
INFO - Adjusting PersistenceUnit db1 &lt;non-jta-data-source&gt; to Resource ID 'database1NonJta' from 'null'
INFO - Configuring PersistenceUnit(name=db2)
INFO - Auto-creating a Resource with id 'database2NonJta' of type 'DataSource for 'db2'.
INFO - Configuring Service(id=database2NonJta, type=Resource, provider-id=database2)
INFO - Adjusting PersistenceUnit db2 &lt;non-jta-data-source&gt; to Resource ID 'database2NonJta' from 'null'
INFO - Configuring PersistenceUnit(name=db3)
INFO - Auto-creating a Resource with id 'database3NonJta' of type 'DataSource for 'db3'.
INFO - Configuring Service(id=database3NonJta, type=Resource, provider-id=database3)
INFO - Adjusting PersistenceUnit db3 &lt;non-jta-data-source&gt; to Resource ID 'database3NonJta' from 'null'
INFO - Enterprise application "/Users/dblevins/examples/dynamic-datasource-routing" loaded.
INFO - Assembling app: /Users/dblevins/examples/dynamic-datasource-routing
INFO - PersistenceUnit(name=router, provider=org.apache.openjpa.persistence.PersistenceProviderImpl) - provider time 504ms
INFO - PersistenceUnit(name=db1, provider=org.apache.openjpa.persistence.PersistenceProviderImpl) - provider time 11ms
INFO - PersistenceUnit(name=db2, provider=org.apache.openjpa.persistence.PersistenceProviderImpl) - provider time 7ms
INFO - PersistenceUnit(name=db3, provider=org.apache.openjpa.persistence.PersistenceProviderImpl) - provider time 6ms
INFO - Jndi(name="java:global/dynamic-datasource-routing/BoostrapUtility!org.superbiz.dynamicdatasourcerouting.BoostrapUtility")
INFO - Jndi(name="java:global/dynamic-datasource-routing/BoostrapUtility")
INFO - Jndi(name="java:global/dynamic-datasource-routing/RoutedPersister!org.superbiz.dynamicdatasourcerouting.RoutedPersister")
INFO - Jndi(name="java:global/dynamic-datasource-routing/RoutedPersister")
INFO - Jndi(name="java:global/EjbModule1519652738/org.superbiz.dynamicdatasourcerouting.DynamicDataSourceTest!org.superbiz.dynamicdatasourcerouting.DynamicDataSourceTest")
INFO - Jndi(name="java:global/EjbModule1519652738/org.superbiz.dynamicdatasourcerouting.DynamicDataSourceTest")
INFO - Created Ejb(deployment-id=RoutedPersister, ejb-name=RoutedPersister, container=Default Stateless Container)
INFO - Created Ejb(deployment-id=org.superbiz.dynamicdatasourcerouting.DynamicDataSourceTest, ejb-name=org.superbiz.dynamicdatasourcerouting.DynamicDataSourceTest, container=Default Managed Container)
INFO - Created Ejb(deployment-id=BoostrapUtility, ejb-name=BoostrapUtility, container=Default Singleton Container)
INFO - Started Ejb(deployment-id=RoutedPersister, ejb-name=RoutedPersister, container=Default Stateless Container)
INFO - Started Ejb(deployment-id=org.superbiz.dynamicdatasourcerouting.DynamicDataSourceTest, ejb-name=org.superbiz.dynamicdatasourcerouting.DynamicDataSourceTest, container=Default Managed Container)
INFO - Started Ejb(deployment-id=BoostrapUtility, ejb-name=BoostrapUtility, container=Default Singleton Container)
INFO - Deployed Application(path=/Users/dblevins/examples/dynamic-datasource-routing)
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.504 sec
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0</code></pre>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<footer>
<div class="container">
<div class="row">
<div class="col-sm-6 text-center-mobile">
<h3 class="white">Be simple. Be certified. Be Tomcat.</h3>
<h5 class="light regular light-white">"A good application in a good server"</h5>
<ul class="social-footer">
<li><a href="https://www.facebook.com/ApacheTomEE/"><i class="fa fa-facebook"></i></a></li>
<li><a href="https://twitter.com/apachetomee"><i class="fa fa-twitter"></i></a></li>
<li><a href="https://plus.google.com/communities/105208241852045684449"><i class="fa fa-google-plus"></i></a></li>
</ul>
</div>
<div class="col-sm-6 text-center-mobile">
<div class="row opening-hours">
<div class="col-sm-3 text-center-mobile">
<h5><a href="../../../latest/docs/" class="white">Documentation</a></h5>
<ul class="list-unstyled">
<li><a href="../../../latest/docs/admin/configuration/index.html" class="regular light-white">How to configure</a></li>
<li><a href="../../../latest/docs/admin/file-layout.html" class="regular light-white">Dir. Structure</a></li>
<li><a href="../../../latest/docs/developer/testing/index.html" class="regular light-white">Testing</a></li>
<li><a href="../../../latest/docs/admin/cluster/index.html" class="regular light-white">Clustering</a></li>
</ul>
</div>
<div class="col-sm-3 text-center-mobile">
<h5><a href="../../../latest/examples/" class="white">Examples</a></h5>
<ul class="list-unstyled">
<li><a href="../../../latest/examples/simple-cdi-interceptor.html" class="regular light-white">CDI Interceptor</a></li>
<li><a href="../../../latest/examples/rest-cdi.html" class="regular light-white">REST with CDI</a></li>
<li><a href="../../../latest/examples/ejb-examples.html" class="regular light-white">EJB</a></li>
<li><a href="../../../latest/examples/jsf-managedBean-and-ejb.html" class="regular light-white">JSF</a></li>
</ul>
</div>
<div class="col-sm-3 text-center-mobile">
<h5><a href="../../../community/index.html" class="white">Community</a></h5>
<ul class="list-unstyled">
<li><a href="../../../community/contributors.html" class="regular light-white">Contributors</a></li>
<li><a href="../../../community/social.html" class="regular light-white">Social</a></li>
<li><a href="../../../community/sources.html" class="regular light-white">Sources</a></li>
</ul>
</div>
<div class="col-sm-3 text-center-mobile">
<h5><a href="../../../security/index.html" class="white">Security</a></h5>
<ul class="list-unstyled">
<li><a href="http://apache.org/security" target="_blank" class="regular light-white">Apache Security</a></li>
<li><a href="http://apache.org/security/projects.html" target="_blank" class="regular light-white">Security Projects</a></li>
<li><a href="http://cve.mitre.org" target="_blank" class="regular light-white">CVE</a></li>
</ul>
</div>
</div>
</div>
</div>
<div class="row bottom-footer text-center-mobile">
<div class="col-sm-12 light-white">
<p>Copyright &copy; 1999-2016 The Apache Software Foundation, Licensed under the Apache License, Version 2.0. Apache TomEE, TomEE, Apache, the Apache feather logo, and the Apache TomEE project logo are trademarks of The Apache Software Foundation. All other marks mentioned may be trademarks or registered trademarks of their respective owners.</p>
</div>
</div>
</div>
</footer>
<!-- Holder for mobile navigation -->
<div class="mobile-nav">
<ul>
<li><a hef="../../../latest/docs/admin/index.html">Administrators</a>
<li><a hef="../../../latest/docs/developer/index.html">Developers</a>
<li><a hef="../../../latest/docs/advanced/index.html">Advanced</a>
<li><a hef="../../../community/index.html">Community</a>
</ul>
<a href="#" class="close-link"><i class="arrow_up"></i></a>
</div>
<!-- Scripts -->
<script src="../../../js/jquery-1.11.1.min.js"></script>
<script src="../../../js/owl.carousel.min.js"></script>
<script src="../../../js/bootstrap.min.js"></script>
<script src="../../../js/wow.min.js"></script>
<script src="../../../js/typewriter.js"></script>
<script src="../../../js/jquery.onepagenav.js"></script>
<script src="../../../js/tree.jquery.js"></script>
<script src="../../../js/highlight.pack.js"></script>
<script src="../../../js/main.js"></script>
</body>
</html>