JBoss - .properties vom Kunden änderbar machen

S

springMe

Gast
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

Top Contributor
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

Top Contributor
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:
Java:
<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

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

y0dA

Top Contributor
Java:
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:
Java:
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?
 
Zuletzt bearbeitet:

FArt

Top Contributor
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

Top Contributor
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:

Java:
	<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?
Java:
    <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>
 
Zuletzt bearbeitet:

y0dA

Top Contributor
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

Top Contributor
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

Top Contributor
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 ;)
 
Zuletzt bearbeitet:

y0dA

Top Contributor
Ok nochmal..

Java:
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 "$"..
 
Zuletzt bearbeitet:
Ähnliche Java Themen
  Titel Forum Antworten Datum
Y Spring JBoss 7 - wie JNDI Datasource einbinden Application Tier 8
M org.jboss.weld.exceptions.UnproxyableResolutionException wegen Parametern im Superclass-Kontruktor Application Tier 10
G jBoss automatische Synchronisation? Application Tier 4
JimPanse JBoss 7 + JPA + Kundera + Cassandra Application Tier 2
B Glassfish vs. Tomcat/JBoss Application Tier 4
S Umstellung von JBoss 7 Full auf Web Profile Application Tier 4
V [JBoss 5] Datenbankverbindung zur Laufzeit aufbauen? Application Tier 8
V [JBoss5] JBoss stoppen bzw. am starten hindern? Application Tier 9
V [JBoss 5] Startroutine implementieren? Application Tier 7
S JBoss, EAR, EJB und ClassNotFound Application Tier 28
V JBoss 5 DeploymentSorter? Application Tier 3
V JBoss 5 mag @Service nicht Application Tier 2
K Threads im JBoss Application Tier 20
S JBoss EJB RMI Application Tier 5
A Jboss Ear mittels maven Application Tier 3
D Lazy Hibernate bei 3-Tier Applikation (JBoss + EJB3 + FatClient) Application Tier 6
ruutaiokwu stateful ejb unter jboss als webservice zur verfügung stellen... Application Tier 2
T Simples EJB-Beispiel für JBoss mit Eclipse Application Tier 3
M Spring - Leichtgewichtiger als JBoss? Application Tier 4
M JBoss Seam - Spring Application Tier 11
N Wie kann ich properties zur Laufzeit aus PropertyPlaceholderConfigurer ermitteln ? Application Tier 2
S Autowired Properties Application Tier 3

Ähnliche Java Themen

Neue Themen


Oben