Tomcat integration
De BlaxWiki
Aller à la navigationAller à la rechercheIntégration d'un environnement Jakarta Tomcat sous Debian GNU/Linux Sarge
Installation des composants[modifier]
Télécharger les sources de Jakarta Tomcat depuis http://jakarta.apache.org/tomcat/index.html. Nous allons mettre en place Tomcat 5.5.9, soit la dernière version stable de la branche 5.x. Les packages Tomcat 4.1.30 et 5.5.9 sont disponible sur Tregor dans / data/mrtg/AdminInstall_Src/ Télécharger en parallèle la JVM -Java Virtual Machine). Nous utilisons ici la JVM de Sun disponible sur http://java.sun.com. La version J2EE-SDK répond à nos besoin, car elle inclue les librairies et applets de bases pour faire fonctionner Tomcat. Sur nos plateformes est installée la version j2sdk, que nous pouvons également trouver sur Tregor dans /data/mrtg /AdminInstall_Src/j2sdk-1.4.2_07-linux-i586.bin. * Il existe des alternatives à la JVM de Sun, notament avec Jrockit disponible sur http://www.bea.com/ Installation de la JVM de Sun Placer l'archive .bin dans /usr/local, puis en tant que root : # sh j2sdk-1.4.2_07-linux-i586.bin Une fois la JVM décompressée, créer un lien symbolique nommé java vers le répertoire j2sdk-1.4.2_07 #pwd /usr/local # ln -s j2sdk-1.4.2_07 java Expoter une variable d'environnement JAVA_HOME qui pointera sur ce lien symbolique # export JAVA_HOME=/usr/local/java Nous pouvons dès à présent déclarer cette variable d'environnement dans notre fichier /etc/profile afin qu'elle soit effective à chaque log in sur le serveur # echo “export JAVA_HOME=/usr/local/java” >> /etc/profile La procédure d'installation de Jrockit est identique.
Installation de Tomcat[modifier]
Placer les sources de Tomcat dans le répertoire /usr/local et décompresser l'archive # cd /usr/local # tar xzf jakarta-tomcat-5.5.9.tar.gz Afin d'assurer la compatibilité avec des softs développés pour Tomcat 4.x, nous allons installer le package jakarta-tomcat-5.5.9-compat.tar.gz, disponible au téléchargement sur Tregor, dans le même répertoire que le reste des sources. Pour installer ce package, le placer au même niveau que jakarta-tomcat-5.5.9.tar.gz, et le décompresser # tar xzf jakarta-tomcat-5.5.9-compat.tar.gz Les fichiers sont décompressés dans le répertoire jakarta-tomcat-5.5.9, donc si nous avons installé Tomcat au préalable, nous n'avons rien a faire, sinon, installer Tomcat après le package compat ne pose pas de problème. Idéalement, nous pouvons créer un lien symbolique tomcat vers jakarta-tomcat-5.5.9 # pwd /usr/local # ln -s jakarta-tomcat-5.5.9 tomcat Exporter une variable d'environnement CATALINA_HOME qui pointera vers /usr/local/tomcat # export CATALINA_HOME=/usr/local/tomcat Nous pouvons également déclarer cette variable d'environnement dans le fichier /etc/profile afin de l'avoir disponible à chaque log in sur le serveur # echo “export CATALINA_HOME=/usr/local/tomcat” >> /etc/profile Voila, notre environnement Java/Tomcat est prêt à fonctionner. Avant de commencer à faire joujou, on va s'installer un compilateur équivalent au make que nous connaissons pour GCC, appelé ANT, qui nous servira à compiler les sources que nous allons utiliser plus loin. Ant permet également de manipuler Tomcat, avec la possibilité d'arrêter et redémarrer des contextes, etc...
Installation de ant[modifier]
Ant est fournit par l'équipe d'Apache. Il peut être téléchargé soit depuis le site officiel http://ant.apache.org, soit depuis Tregor, toujours dans le même répertoire. # pwd /usr/local # tar xzf apache-ant-1.6.2-bin.tar.gz L'idéal est d'avoir le binaire ant dans le PATH. Soit on copie le fichier /usr/local/apache-ant-1.6.2/bin/ant vers /usr/bin, soit on créé un lien symbolique # ln -s /usr/local/apache-ant-1.6.2/bin/ant /usr/bin/ant Soit on ajoute le répertoire /usr/local/apache-ant-1.6.2/bin à sont path # export PATH=$PATH:/usr/local/apache-ant-1.6.2/bin Je vous laisse trouver tout seul pour l'avoir dans le path à chaque log in sur le serveur :) On ajoute également ant dans notre environnement # export ANT_HOME=/usr/local/apache-ant-1.6.2 # echo “export ANT_HOME=/usr/local/apache-ant-1.6.2” >> /etc/profile
Installation des librairies pour le support de MySQL[modifier]
2 librairies sont nécessaires pour que Tomcat puisse communiquer avec une base MySQL. Il s'agit des fichiers mm.mysql-2.0.13-bin.jar et mysql-connector-java-3.0.8-stable-bin.jar. Ces fichiers sont disponible sur Tregor, toujours dans le même répertoire, et également sur le Wiki. Il faut copier ces fichiers dans le répertoire /usr/local/tomcat/common/lib/, puis relancer Tomcat.
Démarrage et arret des services[modifier]
Tomcat est fourni avec 2 scripts permettant de le démarrer et de le stopper. Ces derniers se situent dans le répertoire $CATALINA_HOME/bin/{startup.sh|shutdown.sh}
startup.sh fait appel au fichier catalina.sh qui se situe également dans le répertoire, et qui peut comporter les différentes options qu'il est possible de passer à la JVM. Nous y
reviendrons.
L'idéal en période de configuration est d'avoir une console ouverte en permanence sur le fichier de log de Tomcat afin de suivre le lancement de l'application.
# tail -f /usr/local/tomcat/log/catalina.out
Nous démarrons Tomcat :
# /usr/local/tomcat/bin/startup.sh
Nous pouvons voir dans le log que Tomcat lance chaque contexte les un après les autres. Une fois démarrée, l'application indique dans ses logs :
[...]
Oct 4, 2005 5:36:39 PM org.apache.coyote.http11.Http11Protocol start
INFO: Starting Coyote HTTP/1.1 on http-8080
Oct 4, 2005 5:36:39 PM org.apache.jk.common.ChannelSocket init
INFO: JK: ajp13 listening on /0.0.0.0:8009
Oct 4, 2005 5:36:39 PM org.apache.jk.server.JkMain start
INFO: Jk running ID=0 time=0/63 config=null
Oct 4, 2005 5:36:39 PM org.apache.catalina.storeconfig.StoreLoader load
INFO: Find registry server-registry.xml at classpath resource
Oct 4, 2005 5:36:39 PM org.apache.catalina.startup.Catalina start
INFO: Server startup in 22183 ms
[...]
INFO: Starting Coyote HTTP/1.1 on http-8080
Tomcat écoute maintenant sur le port 8080
INFO: JK: ajp13 listening on /0.0.0.0:8009
Le connector (que nous verront plus loin) écoute sur le port 8009
Afin de s'assurer que Tomcat répond correctement, l'URL http://111.222.333.444:8080/ doit vous retourner la page d'accueil de Tomcat
Démarrage de Tomcat[modifier]
Par défaut, Tomcat n'est pas fournit avec un script d'init. Je vous passe la mise en place de ce script dans les runlevels :) Donc, on a notre Tomcat qui tourne, mais, mis a part pouvoir jouer avec les exemples, on a rien d'autre à faire. Nous allons donc mettre en place 2 petites applications, des CMS (Content management Systems). Un peu de théorie... Tomcat permet de gérer 2 types d'archives, les .war et les .jsp. Le .war pourrait être comparé à une archive contenant l'ensemble des données du site web, et qui sera décompressée et compilée “à la volée” par Tomcat, à condition que l'auto-déploiement soit activé dans la configuration du serveur. Nous verrons plus loin les fichiers de configuration de Tomcat. Les .jsp pourraient être comparés à un fichier .html ou .php, ces derniers ne nécessitent pas de compilation, cependant une structure bien particulière doit être en place dans le répertoire stockant ces pages (WEB-INF/).
Déploiement d'une archive .war[modifier]
Nous allons nous baser sur OpenCMS pour tester le déploiement automatique d'un .war. Télécharger l'archive depuis http://www.opencms.org/opencms/en/download/opencms.html Dézipper le fichier (opencms_6.0.0.zip au moment de la rédaction de ce document), se rendre dans le répertoire opencms nouvellement créé et copier, ou déplacer, le fichier opencms.war vers /usr/local/tomcat/webapps/ Le répertoire /usr/local/tomcat/webapps est l'équivalent du DocumentRoot d'Apache. Si vous avez eu la bonne idée de conserver une console sur le log catalina.out, vous voyez apparaitre au bout de quelques secondes ceci : Oct 4, 2005 6:03:22 PM org.apache.catalina.startup.HostConfig deployWAR INFO: Deploying web application archive opencms.war Et là, oh miracle, si vous vous rendez à l'url http://111.222.333.444:8080/opencms/setup, vous arrivez directement sur l'interface d'installation du soft. Nous passons les détails, il suffit de faire suivant / suivant et de fournir les informations nécessaires pour la connexion à la base de données. Testez le à l'occasion, c'est vraiment un excellent outil. Le principe de déploiement automatique pour les .war est le même quelque soit l'archive.
Compilation d'une application via ANT[modifier]
Pour cet exemple, nous allons donc installer un autre CMS libre, qui répond au doux nom de AtLeap, sous licence “Apache Software Licence”, maintenu par des dévelopeurs Java...
on peut s'attendre à quelque chose de fiable.
On récupère les sources sur https://atleap.dev.java.net/ , on doit se retrouver avec un package nommé atleap-0.5rc2-src.zip (au moment de la rédaction de ce doc.)
On dézippe
# unzip atleap-0.5rc2-src.zip
On entre dans le répertoire atleap-0.5rc2 nouvellement créé. On trouve cette arborescence :
AtLeap.iml
AtLeap.ipr
AtLeap.iws
LICENSE
MD5
NOTICE
README.txt
RELEASE-NOTES
bin
build.properties
build.xml
compile-jsp.xml
lib
metadata
properties.xml
src
test
web
On note 2 fichiers importants, build.properties et build.xml, que nous retrouverons quelque soit l'application à déployer.
build.properties pourrait correspondre au fichier “configure” des sources d'un logiciel. C'est ce fichier qui comporte l'ensemble des paramètres de configuration liés à la
compilation des classes java.
build.xml quand à lui regroupe les paramètres système pour la compilation de l'application, tel que le chemin vers Tomcat, Java, ant, et diverses librairies nécessaires à son
fonctionnement.
Afin que la compilation fonctionne correctement, nous devons éditer le fichier build.properties et y spécifier que nous allons utiliser Tomcat 5.5. Nous devons également y insérer
les paramètres de notre base MySQL.
Rechercher :
[...]
# ============================================================================
# Database Properties
# ============================================================================
[...]
... et adapter le fichier à nos paramètres
[...]
database.jar=${mysql.jar}
database.jar.name=${mysql.jar.name}
database.type=mysql
database.name=${webapp.name}_db
database.host=localhost
database.port=3306
database.driver_class=com.mysql.jdbc.Driver
database.url=jdbc:${database.type}://${database.host}:${database.port}/${database.name}autoReconnect=true&useUnicode=true&characterEncoding=utf-8
database.username=test
database.password=test
database.test.tablename=SELECT 1
database.pool.datasource.class=com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource
database.pool.size.max=20
hibernate.dialect=net.sf.hibernate.dialect.MySQLDialect
dbunit.datatype.factory=org.dbunit.ext.mysql.MySqlDataTypeFactory
dbunit.escapepattern=`?`
[...]
ATTENTION ! atleap nous informe que la base utilisée sera ${webapp.name}_db, donc dans notre cas, atleap_db. Il faut créer cette base manuellement, le script ne le fait pas pour nous
Si nous ne le faisons pas, la compilation se passera bien, mais le déploiement de l'application échouera avec un charmant message du type
[...]
** BEGIN NESTED EXCEPTION **
java.sql.SQLException
MESSAGE: Unknown database 'atleap_db'
STACKTRACE:
java.sql.SQLException: Unknown database 'atleap_db'
[...]
Rechercher :
[...]
# ============================================================================
# Application Server Properties
# ============================================================================
[...]
Commenter les 3 lignes suivantes :
[...]
#appserver.type=jetty5
#appserver.http.port=8080
#appserver.https.port=8443
[...]
et décommenter les lignes relatives à Tomcat 5
[...]
# Please specify tomcat5 or tomcat5.5
appserver.type=tomcat5.5
appserver.http.port=8080
appserver.https.port=8443
[...]
AtLeap vient avec un fichier “build.sh” qui s'occupe de tout pour nous.
#pwd
/some/where/atleap-0.5rc2/bin
# sh build.sh db-prepare
[...]
Buildfile: build.xml
...
BUILD SUCCESSFUL
Total time: 16 seconds
[...]
#pwd
/some/where/atleap-0.5rc2/bin
# sh build.sh db-load
[...]
Buildfile: build.xml
...
...
[war] Building war: /some/where/atleap-0.5rc2/dist/webapps/atleap.war
BUILD SUCCESSFUL
Total time: 32 seconds
[...]
Voila qui est cool :) Les habitués d'Adesium ne seront pas trop dépaysés quand à la sortie de la compilation.
Donc notre .war est prêt à être utilisé. Si nous nous rendons dans le répertoire dist/webapps, nous trouvons 2 fichiers
atleap.war
context.xml
Notre .war donc, et context.xml, qui contient tout les paramètres nécessaires à la mise en place du contexte dans Tomcat.
[...]
<Context path="/atleap" docBase="atleap"
workDir="/usr/local/tomcat5/work/Catalina/localhost/atleap"
debug="99" reloadable="true" antiJARLocking="true">
<Logger className="org.apache.catalina.logger.FileLogger"
prefix="atleap_log." suffix=".txt" timestamp="true"/>
<Resource name="jdbc/atleap_db" auth="Container" type="javax.sql.DataSource"
factory="org.apache.commons.dbcp.BasicDataSourceFactory"
maxActive="20" maxIdle="30" maxWait="10000"
driverClassName="com.mysql.jdbc.Driver"
username="test" password="test"
url="jdbc:mysql://localhost:3306/atleap_db?autoReconnect=true&useUnicode=true&characterEncoding=utf-8"
defaultAutoCommit="true" removeAbandoned="true"
removeAbandonedTimeout="60" logAbandoned="true"/>
</Context>
[...]
Nous allons copier ces 2 fichiers dans notre répertoire webapps
# cp dist/webapps/* /usr/local/tomcat/webapps/
Dans notre console sur le log catalina.out, nous voyons que l'installation du .war est prise en compte
[...]
INFO: Undeploying context [/atleap]
Oct 4, 2005 11:17:53 PM org.apache.catalina.startup.HostConfig deployWAR
INFO: Deploying web application archive atleap.war
AbandonedObjectPool is used (org.apache.commons.dbcp.AbandonedObjectPool@d9ceea)
LogAbandoned: true
RemoveAbandoned: true
RemoveAbandonedTimeout: 60
[atleap] INFO [ContainerBackgroundProcessor[StandardEngine[Catalina]]] MailTemplateLoader.init(53) | MailTemplateLoader: initializing...
[atleap] INFO [ContainerBackgroundProcessor[StandardEngine[Catalina]]] MailTemplateLoader.init(59) | MailTemplateLoader: initialization completed
[atleap] INFO [ContainerBackgroundProcessor[StandardEngine[Catalina]]] StartupListener.initializeMenuRepository(430) | Synchronizing database menu data with data from /WEB-INF/menu-
config.xml
[...]
Si les paramètres MySQL fournit dans le fichier build.properties sont incorrectes, la compilation s'arrête avec une erreur du genre
[...]
** BEGIN NESTED EXCEPTION **
java.sql.SQLException
MESSAGE: Access denied for user: 'test@localhost' (Using password: YES)
STACKTRACE:
java.sql.SQLException: Access denied for user: 'test@localhost' (Using password: YES)
...
[...]
Il faut alors revoir le fichier build.properties et RELANCER les étapes de compilation.
Pour tester notre application, on se rend sur http://111.222.333.444:8080/atleap/
Installer le connector mod_jk pour Apache[modifier]
Nous n'allons aborder que la mise en place de mod_jk vers 1. La version 2 n'étant plus maintenue. Nous aborderons l'installation de mod_jk pour Apache 1.3 et Apache 2.
Récupération des sources et packages nécessaires
Pour Apache 1.3, Debian nous facilite grandement le travail. mod_jk est disponible dans les dépots sous le nom de libapache-mod-jk
# apt-get install libapache-mod-jk
Notre module est installé ici dans /usr/lib/apache/1.3/mod_jk.so . Apache prend en charge immédiatement le module (un restart est tout de même conseillé :)).
Le module est chargé, reste à configurer mod_jk. Nous avons dans ce cas 2 possibilités. Soit nous intégrons les directives de configuration de mod_jk dans la configuration globale
d'Apache, soit nous créons un fichier jk_connector.conf, que nous appelerons ensuite via un Include dans la configuration d'Apache. Nous allons nous baser sur cette seconde
possibilité.
Créer le fichier /etc/apache/jk_connector.conf et y ajouter les lignes suivantes :
<IfModule mod_jk.c>
<VirtualHost 111.222.333.444>
ServerName 111.222.333.444
DocumentRoot /usr/local/tomcat/webapps
JkWorkersFile "/usr/local/tomcat/conf/workers.properties"
Include "/usr/local/tomcat/conf/mod_jk.conf"
JkLogFile "/usr/local/tomcat/logs/mod_jk.log"
JkMount /*.jsp ajp13
JkMount /* ajp13
</VirtualHost>
</IfModule>
A la fin de notre fichier httpd.conf, on ajoute la ligne suivante
Include “/etc/apache/jk_connect.conf”
/usr/local/tomcat/conf/workers.properties
Ce fichier contient toutes les options de configuration de mod_jk. Donc attention à la synthaxe et aux chemins!
workers.tomcat_home=/usr/local/tomcat
ps=/
worker.list=ajp13
worker.ajp13.port=8009
worker.ajp13.host=localhost
worker.ajp13.type=ajp13
worker.inprocess.class_path=$(workers.tomcat_home)$(ps)lib$(ps)tomcat.jar
workers.tomcat_home
Défini le répertoire de base de Tomcat
ps
Indique le séparateur
worker.list
Nom du worker. Très important !!
worker.ajp13.port *
Port du connector déclaré dans la configuration de Tomcat
worker.ajp13.host
Le serveur qui fait tourner Tomcat
worker.ajp13.type *
Type de connector utilisé
worker.inprocess.class_path
Chemin vers le fichier tomcat.jar
* cf détails du fichier server.xml
/usr/local/tomcat/conf/mod_jk.conf
Ce fichier n'est pas primordiale, étant donné que nous passons la majorité des paramètres de mod_jk dans le fichier jk_connect.conf (ensuite en Include dans Apache). Cependant, il
permet de conserver une configuration claire, car nous pouvons ainsi déplacer les options suivantes dans ce fichier :
# Log Level peut etre warning, error ou emerg
JkLogLevel error
JkOptions +ForwardKeySize +ForwardURICompat -ForwardDirectories
JkRequestLogFormat "%w %V %T"
Ce fichier permettrait donc de déclarer l'ensemble des points de montage JkMount afin d'avoir une configuration d'Apache la plus propre possible.
Sous Apache2, l'installation est sensiblement différente. Nous allons devoir compiler mod_jk depuis les sources. Une archive est disponible sur Tregor (jakarta-tomcat-connectors-
jk-1.2.6-src.tar.gz). Attention, libtool est nécessaire.
On décompresse l'archive
# tar xvzf jakarta-tomcat-connectors-jk-1.2.6-src.tar.gz
# cd jakarta-tomcat-connectors-jk-1.2.6-src/build/native
# ./builconf.sh
# ./configure --with-apxs=/usr/bin/apxs2 --with-java-home=${JAVA_HOME}
# make
C'est terminé. Notre module est prêt dans le répertoire apache-2.0/mod_jk.so
# cp apache-2.0/mod_jk.so /usr/lib/apache2/modules/.
La configuration de mod_jk reste identique quelque soit la version d'Apache utilisée.
Détail des répertoires et fichiers[modifier]
Si nous nous plaçons à la racine de Tomcat, nous trouvons les répertoires suivants : bin common conf logs server shared temp webapps work bin contient l'ensemble des fichiers exécutables (startup.sh, shutdown.sh, catalina.sh, ...) common contient les éléments partagés entre les diverses webapps installées. conf contient les fichiers de configuration (sérieux ?! :)) logs contient les fichiers de logs (...) server contient les éléments propres au serveur Tomcat, tel que le manager. shared pas encore totalement identifié ... à voir! temp répertoire temporaire de Tomcat (...) webapps répertoire contenant par défaut l'ensemble des applications gérées par Tomcat work contient l'ensemble des applications déployées lors du démarrage de Tomcat. Nous n'allons pas nous éterniser sur tout les répertoires, mais uniquement voir le contenu du répertoire conf/, qui contient les éléments les plus importants, tant au niveau du fonctionnement du serveur que de la configuration des applications web. server.xml Déjà, par défaut, les ¾ des trucs sont inutiles, mais servent d'exemple. C'est du XML, donc si une balise est mal fermée, Tomcat va raler et refuser de démarrer en générant une erreur critique. Pour info, la syntaxe XML, c'est : <balise contenu=”coucou”></balise> ou <balise contenu=”coucou” /> Ce fichier nous indiquera le port utilisé par Tomcat (Connector port="8080"), le port du connector (Connector port="8009") et son protocole (protocol="AJP/1.3"), le hostname (Host name="localhost". Ce point n'a pas de grosse incidence si un seul domaine est déclaré sur le serveur. Nous avons dans le même bloc que hostname l'emplacement des documents par défaut pour ce hostname (appBase="webapps"), la possibilité d'autoriser le déploiement automatique d'applications (autoDeploy="true"), le déploiement automatique de fichiers .war (unpackWARs="true"). Exemple : <Host name="www.mondomaine.fr" debug="0" unpackWARs="true"> <Context path="" docBase="/usr/local/tomcat/webapps/mondomaine" debug="0" reloadable="true"/> </Host> <Host name="www.tondomaine.fr" debug="0" unpackWARs="true"> <Context path="" docBase="/usr/local/tomcat/webapps/tondomaine" debug="0" reloadable="true"/> </Host> On voit ici les 2 syntaxes XML. web.xml Ce fichier ne va gérer que la section web de Tomcat. Nous pourrons y déclarer les fichiers faisant office d'index, les TypeMime, etc ... Ce fichier est extrémement documenté, donc ne pas hésiter à y jeter un oeil :) tomcat-users.xml C'est dans ce fichier que les utilisateurs seront déclarés pour l'accès au manager ou à tout autre éléments utilisant ce fichier pour l'authentification. Sous Apache 1.3, il est possible d'utiliser les .htaccess. Les tests réalisés jusqu'à maintenant avec Apache 2 ont échoués, le .htaccess n'étant pas pris en compte. Exemple: <?xml version='1.0' encoding='utf-8'?> <tomcat-users> <role rolename="tomcat"/> <role rolename="role1"/> <role rolename="manager"/> <user username="tomcat" password="tomcat" roles="tomcat"/> <user username="role1" password="tomcat" roles="role1"/> <user username="both" password="tomcat" roles="tomcat,role1,manager"/> </tomcat-users> On déclare donc les rôles, puis les users, auquels ont attribue les rôles. Rien de bien compliqué ici.
Résolution de problème[modifier]
Tomcat ne démarre pas Neither the JAVA_HOME nor the JRE_HOME environment variable is defined At least one of these environment variable is needed to run this program ou Neither the TOMCAT_HOME nor the CATALINA_HOME environment variable is defined At least one of these environment variable is needed to run this program L'erreur est explicite. Les variables d'environnement nécessaires au fonctionnement de Tomcat ne sont pas déclarées. # export JAVA_HOME=/usr/local/java # export CATALINA_HOME=/usr/local/tomcat Tomcat plante avec un erreur du type « OutOfMemory » Aïe, pas cool :-) Si la machine dispose d'assez de RAM (1G est le minimum), on peut alors alloué plus de mémoire à Java. Pour cela, on édite le fichier /usr/local/tomcat /bin/catalina.sh et l'on ajoute la variable JAVA_OPTS avec nos options. Exemple: JAVA_OPTS="-Xss256k -Xms512m -Xmx512m" Un restart de Tomcat est nécessaire. La mémoire nécessaire à Tomcat/Java est dépendante du nombre de contextes à déployer. Si l'erreur réapparait, il faut alors augmenter progressivement la valeur de -Xms et -Xmx, par tranche de 32M par exemple. L'idéal étant de rajouter de la RAM.