# JBoss - .properties vom Kunden änderbar machen



## springMe (15. Nov 2012)

Hi!
Folgende Sachlage:
Ich habe hier einen JBoss AS 7.1.1 und eine Webapplikation (Spring, Hibernate, JSON, JQuery) und nun habe ich die Aufgabe dass gewisse .properties Dateien vom Kunden adaptiert werden wollen. Sprich die .properties nichts in .war sondern ins Filesystem oder dergleichen, hierbei kommt aber hinzu dass die Applikation beim Kunden natürlich in einem Cluster läuft.

Hat hiermit schon jemand Erfahrungen gemacht und kann mir einen Tipp geben?

Natürlich gibt es JBoss Modules (MBean?) jedoch dürfte das auch nicht so ohne weiteres im Cluster funktionieren? Ein shared directory kommt leider auch nicht in Frage somit habe ich noch eine andere Variante in Betracht gezogen, nämlich in der Applikation eine Adminseite zur Verfügung zu stellen wo div. Properties ausgelesen und verändert werden können. Hierbei gibts aber natürlich auch Probleme, nämlich dass beim restart des JBoss dann alle Änderungen an den .properties obsolet, weil nicht mehr vorhanden, wären da der JBoss anscheinend nicht "physisch" deployed? D.h. nach dem restart sind wieder die .properties vom .war die aktuellesten da selbige ja nicht wirklich überschrieben wurden.

Nun habe ich mir gedacht dann lege ich die .properties (jene die vom Kunden änderbar sein sollen sind eh in einem überschaubaren Rahmen) einfach in die Datenbank. Hierbei gibts natürlich wieder das Problem dass manche dieser Properties in der applicationContext.xml vom Spring benötigt werden und ich hierbei sicher nicht auf die Datenbank zugreifen kann?

Bin für alle Vorschläge offen.


----------



## FArt (16. Nov 2012)

Die Konfiguration sollte über einen Service geschehen. Vermutlich ist es tatsächlich das einfachste, diese Werte in einer DB zu halten. Von irgendwelchen Workarounds mit Propertiesdateien rate ich ab.


----------



## y0dA (16. Nov 2012)

Wäre auch dafür sie in der Datenbank abzulegen, nur wie kann ich dann in der applicationContext.xml darauf zugreifen? Habe im Netz ein paar uralt Beispiele gesehen, kann mir aber nicht vorstellen dass es da nicht eine aktueller Art der Verwendung gibt?

Sprich wie kann ich die Properties aus der Datenbank hier hinzufügen:

```
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:database.properties</value>
                <value>classpath:configuration.properties</value>
                <value>classpath:mail.properties</value>
            </list>
        </property>
```


----------



## FArt (16. Nov 2012)

Du musst nur eine Implementierung von PropertyPlaceholderConfigurer implementieren (evtl. davon ableiten), die Properties aus deiner alternativen Quelle (z.B. DB) ermittelt.


----------



## y0dA (19. Nov 2012)

```
public class JdbcPropertyPlaceholderConfigurer extends
		PropertyPlaceholderConfigurer {

	private Logger log = Logger
			.getLogger(JdbcPropertyPlaceholderConfigurer.class);

	private JdbcTemplate jdbcTemplate;

	private String nameColumn;

	private String valueColumn;

	private String propertiesTable;

	/**
	 * Provide a different prefix
	 */
	public JdbcPropertyPlaceholderConfigurer() {
		super();
		setPlaceholderPrefix("#{");
	}

	@Override
	protected void loadProperties(final Properties props) throws IOException {
		if (null == props) {
			throw new IOException(
					"No properties passed by Spring framework - cannot proceed");
		}
		String sql = String.format("select %s, %s from %s", nameColumn,
				valueColumn, propertiesTable);
		log.info("Reading configuration properties from database");
		try {
			jdbcTemplate.query(sql, new RowCallbackHandler() {

				public void processRow(ResultSet rs) throws SQLException {
					String name = rs.getString(nameColumn);
					String value = rs.getString(valueColumn);
					if (null == name || null == value) {
						throw new SQLException(
								"Configuration database contains empty data. Name='"
										+ name + "' Value='" + value + "'");
					}
					props.setProperty(name, value);
				}

			});
		} catch (Exception e) {
			log.fatal("There is an error in either 'application.properties' or the configuration database.");
			throw new IOException(e);
		}
		if (props.size() == 0) {
			log.fatal("The configuration database could not be reached or does not contain any properties in '"
					+ propertiesTable + "'");
		}
	}

	public void setJdbcTemplate(DataSource dataSource) {
		this.jdbcTemplate = new JdbcTemplate(dataSource);
	}

	// public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
	// this.jdbcTemplate = jdbcTemplate;
	// }

	public void setNameColumn(String nameColumn) {
		this.nameColumn = nameColumn;
	}

	public void setValueColumn(String valueColumn) {
		this.valueColumn = valueColumn;
	}

	public void setPropertiesTable(String propertiesTable) {
		this.propertiesTable = propertiesTable;
	}
}
```

applicationContext.xml snippet:
[XML]
    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:database.properties</value>
            </list>
        </property>
    </bean>

    <bean id="entityManagerFactory"
          class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="persistenceUnitName" value="myPersistenceUnit" />
        <property name="dataSource" ref="dataSource" />
        <property name="persistenceXmlLocation" value="classpath*:META-INF/persistence.xml"/>
   		<property name="jpaVendorAdapter">
			<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
				<property name="databasePlatform" value="org.hibernate.dialect.SQLServerDialect" />
				<property name="showSql" value="false" />
				<property name="generateDdl" value="false" />
			</bean>
		</property>
	</bean>

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
        <property name="dataSource" ref="dataSource" />
    </bean>

    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
          destroy-method="close">
        <property name="driverClass" value="${database.driverClassName}" />
        <property name="jdbcUrl" value="${database.url}" />
        <property name="user" value="${database.username}" />
        <property name="password" value="${database.password}" />
        <!-- C3P0 connection pool configuration -->
        <property name="minPoolSize" value="5" />
        <property name="maxPoolSize" value="50" />
        <property name="initialPoolSize" value="10" />
        <property name="checkoutTimeout" value="0" />
        <property name="maxStatements" value="50" />
        <property name="maxIdleTime" value="300" />
    </bean>

	<!-- Enable configuration through the JDBC configuration with fall-through 
		to framework.properties -->
	<bean id="jdbcProperties" class="com.myproject.persistence.JdbcPropertyPlaceholderConfigurer">
		<property name="ignoreUnresolvablePlaceholders" value="false" />
		<property name="order" value="2" />
		<property name="nameColumn" value="propKey" />
		<property name="valueColumn" value="propValue" />
		<property name="propertiesTable" value="prop_configuration" />
		<property name="jdbcTemplate" ref="dataSource" />
	</bean>
[/XML]

Exception beim ausführen der Query:

```
org.springframework.beans.factory.BeanInitializationException: Could not load properties; nested exception is java.io.IOException: org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is java.sql.SQLException: Connections could not be acquired from the underlying database!
	at org.springframework.beans.factory.config.PropertyResourceConfigurer.postProcessBeanFactory(PropertyResourceConfigurer.java:87)
	at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:681)
	at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:656)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:446)
	at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:384)
	at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:283)
	at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:111)
	at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4135)
	at org.apache.catalina.core.StandardContext.start(StandardContext.java:4630)
	at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1045)
	at org.apache.catalina.core.StandardHost.start(StandardHost.java:785)
	at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1045)
	at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:445)
	at org.apache.catalina.startup.Embedded.start(Embedded.java:825)
	at org.codehaus.mojo.tomcat.AbstractRunMojo.startContainer(AbstractRunMojo.java:558)
	at org.codehaus.mojo.tomcat.AbstractRunMojo.execute(AbstractRunMojo.java:255)
	at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:101)
	at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:209)
	at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:153)
	at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:145)
	at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:84)
	at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:59)
	at org.apache.maven.lifecycle.internal.LifecycleStarter.singleThreadedBuild(LifecycleStarter.java:183)
	at org.apache.maven.lifecycle.internal.LifecycleStarter.execute(LifecycleStarter.java:161)
	at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:320)
	at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:156)
	at org.apache.maven.cli.MavenCli.execute(MavenCli.java:537)
	at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:196)
	at org.apache.maven.cli.MavenCli.main(MavenCli.java:141)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:601)
	at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:290)
	at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:230)
	at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:409)
	at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:352)
Caused by: java.io.IOException: org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is java.sql.SQLException: Connections could not be acquired from the underlying database!
	at com.myproject.persistence.JdbcPropertyPlaceholderConfigurer.loadProperties(JdbcPropertyPlaceholderConfigurer.java:73)
	at org.springframework.core.io.support.PropertiesLoaderSupport.mergeProperties(PropertiesLoaderSupport.java:161)
	at org.springframework.beans.factory.config.PropertyResourceConfigurer.postProcessBeanFactory(PropertyResourceConfigurer.java:78)
	... 36 more
Caused by: org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is java.sql.SQLException: Connections could not be acquired from the underlying database!
	at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:80)
	at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:382)
	at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:456)
	at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:460)
	at com.myproject.persistence.JdbcPropertyPlaceholderConfigurer.loadProperties(JdbcPropertyPlaceholderConfigurer.java:57)
	... 38 more
Caused by: java.sql.SQLException: Connections could not be acquired from the underlying database!
	at com.mchange.v2.sql.SqlUtils.toSQLException(SqlUtils.java:106)
	at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool.checkoutPooledConnection(C3P0PooledConnectionPool.java:529)
	at com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource.getConnection(AbstractPoolBackedDataSource.java:128)
	at org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:111)
	at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:77)
	... 42 more
Caused by: com.mchange.v2.resourcepool.CannotAcquireResourceException: A ResourcePool could not acquire a resource from its primary factory or source.
	at com.mchange.v2.resourcepool.BasicResourcePool.awaitAvailable(BasicResourcePool.java:1319)
	at com.mchange.v2.resourcepool.BasicResourcePool.prelimCheckoutResource(BasicResourcePool.java:557)
	at com.mchange.v2.resourcepool.BasicResourcePool.checkoutResource(BasicResourcePool.java:477)
	at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool.checkoutPooledConnection(C3P0PooledConnectionPool.java:525)
	... 45 more
```

Ich glaub die Bean "dataSource" gibts zu dem Zeitpunkt noch gar nicht?


----------



## FArt (19. Nov 2012)

y0dA hat gesagt.:


> Ich glaub die Bean "dataSource" gibts zu dem Zeitpunkt noch gar nicht?


Davon würde ich ausgehen. Die Properties werden in der Initialisierungsphase gesetzt. Da kann man mit den Beans natürlich noch nicht arbeiten. Regel den Zugriff direkt, ohne Springbeans, die gerade erst mal initialisiert sind.


----------



## y0dA (19. Nov 2012)

FArt hat gesagt.:


> Davon würde ich ausgehen. Die Properties werden in der Initialisierungsphase gesetzt. Da kann man mit den Beans natürlich noch nicht arbeiten. Regel den Zugriff direkt, ohne Springbeans, die gerade erst mal initialisiert sind.



Das Problem liegt hierbei aber dass die Datenbank Sachen (url,driver,user,pwd) in einem separaten .properties File liegen und auf selbiges kann ich aber, zu dem Zeitpunkt, anscheinend auch noch nicht zugreifen. Hatte folgendes probiert:


```
<bean id="jdbcProperties" class="com.myproject.persistence.JdbcPropertyPlaceholderConfigurer">
		<property name="ignoreUnresolvablePlaceholders" value="false" />
		<property name="order" value="2" />
		<property name="nameColumn" value="propKey" />
		<property name="valueColumn" value="propValue" />
		<property name="propertiesTable" value="prop_configuration" />
		<property name="driverClass" value="${database.driverClassName}" />
        <property name="jdbcUrl" value="${database.url}" />
        <property name="user" value="${database.username}" />
        <property name="password" value="${database.password}" />
	</bean>
```

In der Klasse stehen dann in den jeweiligen String Objekten nicht der Value des Properties drinnen sondern bspw. "${database.password}"..

Abgesehen davon mit welcher Klasse soll ich "DataSource" initialisieren? "BasicDataSource" hab ich nicht im classpath..DriverManagerDataSource?

Und abschliessend muss ich dann diese von der db geladenen properties so adden?

```
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:database.properties</value>
                <value>classpath:configuration.properties</value>
                <ref bean="jdbcProperties"/>
            </list>
        </property>
    </bean>
```


----------



## y0dA (19. Nov 2012)

Ok bin wohl ein wenig durcheinander gekommen. Oben extende ich ja von PropertyPlaceholderConfigurer und versuche dann selbige Bean dem PropertyPlaceholderConfigurer hinzuzufügen - kann nicht funktionieren.

Was ist nun die bessere Herangehensweise, soll ich quasi PropertyPlaceholderConfigurer überschreiben (wie soll ich das umsetzen, will ja paar properties aus der DB und ein paar von anderen .properties Dateien; weiters sollen die geladenen Properties natürlich in der applicationContext.xml schon verfügbar sein damit mir die Platzhalter "${xyz}" überschrieben werden. 

Oder soll ich versuchen dem PropertyPlaceholderConfigurer einfach meine Klasse, welche die properties aus der DB holt, unter locations hinzuzufügen? Nur wovon muss da meiner Klasse erben?


----------



## FArt (19. Nov 2012)

Prinzipiell kannst du glaube ich beides machen. Ich denke du kannst mehrere PlaceholderConfigurer anbieten, die dann der Reihe nach ihre Arbeit verrichten. So lange die dort hinterlegten Properties disjunkt sind, ist das gar kein Thema, sonst ersetzt halt der erste.

Aber du kannst auch mit einer Ableitung dafür sorgen, dass ein Configurer alle Properties aus verschiedenen Quellen ersetzt.


----------



## y0dA (19. Nov 2012)

Könntest du mal bitte bissl konkreter werden, ich stehe hier einfach an. Oben genannter Code funktioniert einfach nicht..
Du meinst ich kann einfach den bisherigen PlaceholderConfigurer so lassen und darunter einfach noch einen (Also eine weitere bean) definieren welcher dann mein JdbcPlaceholderConfigurer ist? Den anderen Weg schaffe ich nicht weil ich nicht weiß von welcher Klasse ich ableiten muss damit der Compiler nicht meckert


----------



## y0dA (19. Nov 2012)

Ok nochmal..


```
public class JdbcPropertyPlaceholderConfigurer extends
		PropertyPlaceholderConfigurer {

	private Logger log = Logger
			.getLogger(JdbcPropertyPlaceholderConfigurer.class);

	private JdbcTemplate jdbcTemplate;

	private String nameColumn;

	private String valueColumn;

	private String propertiesTable;

	private String password;

	/**
	 * Provide a different prefix
	 */
	public JdbcPropertyPlaceholderConfigurer() {
		super();
		setPlaceholderPrefix("#{");

		 DriverManagerDataSource ds = new DriverManagerDataSource();
		 ds.setDriverClassName("net.sourceforge.jtds.jdbc.Driver");
		 ds.setUrl("jdbc:jtds:sqlserver://...:..../");
		 ds.setUsername("user");
		 ds.setPassword("pwd");
		
		 this.jdbcTemplate = new JdbcTemplate(ds);
	}

	@Override
//FIXME muss ich in dieser Methode noch irgendetwas machen damit die properties erkannt werden?
	protected void loadProperties(final Properties props) throws IOException {
		if (null == props) {
			throw new IOException(
					"No properties passed by Spring framework - cannot proceed");
		}
		String sql = String.format("select %s, %s from %s", nameColumn,
				valueColumn, propertiesTable);
		log.info("Reading configuration properties from database");
		try {
			jdbcTemplate.query(sql, new RowCallbackHandler() {

				public void processRow(ResultSet rs) throws SQLException {
					String name = rs.getString(nameColumn);
					String value = rs.getString(valueColumn);
					if (null == name || null == value) {
						throw new SQLException(
								"Configuration database contains empty data. Name='"
										+ name + "' Value='" + value + "'");
					}
					props.setProperty(name, value);
					System.out.println(name + " " + value);
				}

			});
		} catch (Exception e) {
			log.fatal("There is an error in either 'application.properties' or the configuration database.");
			throw new IOException(e);
		}
		if (props.size() == 0) {
			log.fatal("The configuration database could not be reached or does not contain any properties in '"
					+ propertiesTable + "'");
		}
	}

	public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
	}

	public void setNameColumn(String nameColumn) {
		this.nameColumn = nameColumn;
	}

	public void setValueColumn(String valueColumn) {
		this.valueColumn = valueColumn;
	}

	public void setPropertiesTable(String propertiesTable) {
		this.propertiesTable = propertiesTable;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}
```

[XML] <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="order" value="1"/>
        <property name="ignoreUnresolvablePlaceholders" value="true"/>
        <property name="locations">
            <list>
                <value>classpath:database.properties</value>
                <value>classpath:configuration.properties</value>
            </list>
        </property>
    </bean>

	<bean
		class="com.myproject.persistence.JdbcPropertyPlaceholderConfigurer">
		<property name="order" value="2" />
		<property name="ignoreUnresolvablePlaceholders" value="true" />
		<property name="nameColumn" value="propKey" />
		<property name="valueColumn" value="propValue" />
		<property name="propertiesTable" value="prop_configuration" />
	</bean>

<bean id="appProperties" 
          class="org.springframework.beans.factory.config.PropertiesFactoryBean">
        <property name="singleton" value="true"/>

        <property name="properties">
                <props>
                    <prop key="irgendeine.url">${irgendeine.url}</prop>
<!-- Hier gibt es dann ein Problem, und zwar wird dann ${irgendeine.url} als value herangezogen
und nicht der tatsächliche Wert welcher zuvor in JdbcPropertyPlaceholderConfigurer geladen wurde. Die properties aus den .properties werden erkannt. -->
                </props>
        </property>
	</bean>[/XML]

Also mit oben genannter Konfiguration werden beide PlaceholderConfigurer durchlaufen, es wird auch alles schön aus der DB geladen nur wenn ich dann aus der Bean appProperties ein Property rausholen möchte welches aus der DB ist dann wird der Platzhalter retouniert und nicht der Wert, sprich der Platzhalter wird von Spring nicht überschrieben..

**EDIT**
Hab das Problem gefunden! Das PlaceholderPrefix habe ich im Konstruktor geändert auf "#", angesprochen habe ich es aber mit "$"..


----------

