/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.component.jpa;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.LockModeType;
import org.apache.camel.Consumer;
import org.apache.camel.Exchange;
import org.apache.camel.Expression;
import org.apache.camel.InvalidPayloadException;
import org.apache.camel.InvalidPayloadRuntimeException;
import org.apache.camel.PollingConsumer;
import org.apache.camel.Processor;
import org.apache.camel.Producer;
import org.apache.camel.impl.ScheduledPollEndpoint;
import org.apache.camel.spi.Metadata;
import org.apache.camel.spi.UriEndpoint;
import org.apache.camel.spi.UriParam;
import org.apache.camel.spi.UriPath;
import org.apache.camel.support.ExpressionAdapter;
import org.apache.camel.util.CastUtils;
import org.apache.camel.util.IntrospectionSupport;
import org.apache.camel.util.ObjectHelper;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalEntityManagerFactoryBean;
import org.springframework.orm.jpa.SharedEntityManagerCreator;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.TransactionTemplate;
/**
* The jpa component enables you to store and retrieve Java objects from databases using JPA.
*/
@UriEndpoint(firstVersion = "1.0.0", scheme = "jpa", title = "JPA", syntax = "jpa:entityType", consumerClass = JpaConsumer.class, label = "database,sql")
public class JpaEndpoint extends ScheduledPollEndpoint {
private EntityManagerFactory entityManagerFactory;
private PlatformTransactionManager transactionManager;
private Expression producerExpression;
@UriPath(description = "Entity class name") @Metadata(required = "true")
private Class<?> entityType;
@UriParam(defaultValue = "camel") @Metadata(required = "true")
private String persistenceUnit = "camel";
@UriParam(defaultValue = "true")
private boolean joinTransaction = true;
@UriParam
private boolean sharedEntityManager;
@UriParam(defaultValue = "-1")
private int maximumResults = -1;
@UriParam(label = "consumer", defaultValue = "true")
private boolean consumeDelete = true;
@UriParam(label = "consumer", defaultValue = "true")
private boolean consumeLockEntity = true;
@UriParam(label = "consumer")
private int maxMessagesPerPoll;
@UriParam(optionalPrefix = "consumer.")
private String query;
@UriParam(optionalPrefix = "consumer.")
private String namedQuery;
@UriParam(optionalPrefix = "consumer.")
private String nativeQuery;
@UriParam(label = "consumer", optionalPrefix = "consumer.", defaultValue = "PESSIMISTIC_WRITE")
private LockModeType lockModeType = LockModeType.PESSIMISTIC_WRITE;
@UriParam(optionalPrefix = "consumer.", multiValue = true)
private Map<String, Object> parameters;
@UriParam(optionalPrefix = "consumer.")
private Class<?> resultClass;
@UriParam(label = "consumer", optionalPrefix = "consumer.")
private boolean transacted;
@UriParam(label = "consumer", optionalPrefix = "consumer.")
private boolean skipLockedEntity;
@UriParam(label = "consumer", optionalPrefix = "consumer.")
private DeleteHandler<Object> deleteHandler;
@UriParam(label = "consumer", optionalPrefix = "consumer.")
private DeleteHandler<Object> preDeleteHandler;
@UriParam(label = "producer", defaultValue = "true")
private boolean flushOnSend = true;
@UriParam(label = "producer")
private boolean usePersist;
@UriParam(label = "producer")
private boolean usePassedInEntityManager;
@UriParam(label = "producer")
private boolean remove;
@UriParam(label = "producer")
private Boolean useExecuteUpdate;
@UriParam(label = "advanced", prefix = "emf.", multiValue = true)
private Map<String, Object> entityManagerProperties;
public JpaEndpoint() {
}
/**
* @deprecated use {@link JpaEndpoint#JpaEndpoint(String, JpaComponent)} instead
*/
@Deprecated
public JpaEndpoint(String endpointUri) {
super(endpointUri);
}
public JpaEndpoint(String uri, JpaComponent component) {
super(uri, component);
entityManagerFactory = component.getEntityManagerFactory();
transactionManager = component.getTransactionManager();
}
/**
* @deprecated use {@link JpaEndpoint#JpaEndpoint(String, JpaComponent)} instead
*/
@Deprecated
public JpaEndpoint(String endpointUri, EntityManagerFactory entityManagerFactory) {
super(endpointUri);
this.entityManagerFactory = entityManagerFactory;
}
/**
* @deprecated use {@link JpaEndpoint#JpaEndpoint(String, JpaComponent)} instead
*/
@Deprecated
public JpaEndpoint(String endpointUri, EntityManagerFactory entityManagerFactory, PlatformTransactionManager transactionManager) {
super(endpointUri);
this.entityManagerFactory = entityManagerFactory;
this.transactionManager = transactionManager;
}
@Override
public JpaComponent getComponent() {
return (JpaComponent) super.getComponent();
}
public Producer createProducer() throws Exception {
validate();
JpaProducer producer = new JpaProducer(this, getProducerExpression());
producer.setQuery(getQuery());
producer.setNamedQuery(getNamedQuery());
producer.setNativeQuery(getNativeQuery());
producer.setParameters(getParameters());
producer.setResultClass(getResultClass());
producer.setUseExecuteUpdate(isUseExecuteUpdate());
return producer;
}
public Consumer createConsumer(Processor processor) throws Exception {
validate();
JpaConsumer consumer = new JpaConsumer(this, processor);
consumer.setMaxMessagesPerPoll(getMaxMessagesPerPoll());
consumer.setQuery(getQuery());
consumer.setNamedQuery(getNamedQuery());
consumer.setNativeQuery(getNativeQuery());
consumer.setLockModeType(getLockModeType());
consumer.setParameters(getParameters());
consumer.setResultClass(getResultClass());
consumer.setTransacted(isTransacted());
consumer.setSkipLockedEntity(isSkipLockedEntity());
consumer.setDeleteHandler(getDeleteHandler());
consumer.setPreDeleteHandler(getPreDeleteHandler());
configureConsumer(consumer);
return consumer;
}
@Override
public PollingConsumer createPollingConsumer() throws Exception {
JpaPollingConsumer consumer = new JpaPollingConsumer(this);
consumer.setQuery(getQuery());
consumer.setNamedQuery(getNamedQuery());
consumer.setNativeQuery(getNativeQuery());
consumer.setLockModeType(getLockModeType());
consumer.setParameters(getParameters());
consumer.setResultClass(getResultClass());
return consumer;
}
@Override
public void configureProperties(Map<String, Object> options) {
super.configureProperties(options);
Map<String, Object> emProperties = IntrospectionSupport.extractProperties(options, "emf.");
if (emProperties != null) {
setEntityManagerProperties(emProperties);
}
}
public boolean isSingleton() {
return true;
}
@Override
protected String createEndpointUri() {
return "jpa" + (entityType != null ? "://" + entityType.getName() : "");
}
// Properties
// -------------------------------------------------------------------------
public Expression getProducerExpression() {
if (producerExpression == null) {
producerExpression = createProducerExpression();
}
return producerExpression;
}
public void setProducerExpression(Expression producerExpression) {
this.producerExpression = producerExpression;
}
public int getMaximumResults() {
return maximumResults;
}
/**
* Set the maximum number of results to retrieve on the Query.
*/
public void setMaximumResults(int maximumResults) {
this.maximumResults = maximumResults;
}
public Class<?> getEntityType() {
return entityType;
}
/**
* The JPA annotated class to use as entity.
*/
public void setEntityType(Class<?> entityType) {
this.entityType = entityType;
}
public EntityManagerFactory getEntityManagerFactory() {
if (entityManagerFactory == null) {
entityManagerFactory = createEntityManagerFactory();
}
return entityManagerFactory;
}
/**
* The {@link EntityManagerFactory} to use.
*/
public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) {
this.entityManagerFactory = entityManagerFactory;
}
public PlatformTransactionManager getTransactionManager() {
if (transactionManager == null) {
transactionManager = createTransactionManager();
}
return transactionManager;
}
/**
* To use the {@link PlatformTransactionManager} for managing transactions.
*/
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
/**
* Additional properties for the entity manager to use.
*/
public Map<String, Object> getEntityManagerProperties() {
if (entityManagerProperties == null) {
entityManagerProperties = CastUtils.cast(System.getProperties());
}
return entityManagerProperties;
}
public void setEntityManagerProperties(Map<String, Object> entityManagerProperties) {
this.entityManagerProperties = entityManagerProperties;
}
public String getPersistenceUnit() {
return persistenceUnit;
}
/**
* The JPA persistence unit used by default.
*/
public void setPersistenceUnit(String persistenceUnit) {
this.persistenceUnit = persistenceUnit;
}
public boolean isConsumeDelete() {
return consumeDelete;
}
/**
* If true, the entity is deleted after it is consumed; if false, the entity is not deleted.
*/
public void setConsumeDelete(boolean consumeDelete) {
this.consumeDelete = consumeDelete;
}
public boolean isConsumeLockEntity() {
return consumeLockEntity;
}
/**
* Specifies whether or not to set an exclusive lock on each entity bean while processing the results from polling.
*/
public void setConsumeLockEntity(boolean consumeLockEntity) {
this.consumeLockEntity = consumeLockEntity;
}
public boolean isFlushOnSend() {
return flushOnSend;
}
/**
* Flushes the EntityManager after the entity bean has been persisted.
*/
public void setFlushOnSend(boolean flushOnSend) {
this.flushOnSend = flushOnSend;
}
public int getMaxMessagesPerPoll() {
return maxMessagesPerPoll;
}
/**
* An integer value to define the maximum number of messages to gather per poll.
* By default, no maximum is set. Can be used to avoid polling many thousands of messages when starting up the server.
* Set a value of 0 or negative to disable.
*/
public void setMaxMessagesPerPoll(int maxMessagesPerPoll) {
this.maxMessagesPerPoll = maxMessagesPerPoll;
}
public boolean isUsePersist() {
return usePersist;
}
/**
* Indicates to use entityManager.persist(entity) instead of entityManager.merge(entity).
* Note: entityManager.persist(entity) doesn't work for detached entities
* (where the EntityManager has to execute an UPDATE instead of an INSERT query)!
*/
public void setUsePersist(boolean usePersist) {
this.usePersist = usePersist;
}
public boolean isRemove() {
return remove;
}
/**
* Indicates to use entityManager.remove(entity).
*/
public void setRemove(boolean isRemove) {
this.remove = isRemove;
}
public boolean isJoinTransaction() {
return joinTransaction;
}
/**
* The camel-jpa component will join transaction by default.
* You can use this option to turn this off, for example if you use LOCAL_RESOURCE and join transaction
* doesn't work with your JPA provider. This option can also be set globally on the JpaComponent,
* instead of having to set it on all endpoints.
*/
public void setJoinTransaction(boolean joinTransaction) {
this.joinTransaction = joinTransaction;
}
public boolean isUsePassedInEntityManager() {
return this.usePassedInEntityManager;
}
/**
* If set to true, then Camel will use the EntityManager from the header
* JpaConstants.ENTITYMANAGER instead of the configured entity manager on the component/endpoint.
* This allows end users to control which entity manager will be in use.
*/
public void setUsePassedInEntityManager(boolean usePassedIn) {
this.usePassedInEntityManager = usePassedIn;
}
public boolean isSharedEntityManager() {
return sharedEntityManager;
}
/**
* Whether to use Spring's SharedEntityManager for the consumer/producer.
* Note in most cases joinTransaction should be set to false as this is not an EXTENDED EntityManager.
*/
public void setSharedEntityManager(boolean sharedEntityManager) {
this.sharedEntityManager = sharedEntityManager;
}
public String getQuery() {
return query;
}
/**
* To use a custom query.
*/
public void setQuery(String query) {
this.query = query;
}
public String getNamedQuery() {
return namedQuery;
}
/**
* To use a named query.
*/
public void setNamedQuery(String namedQuery) {
this.namedQuery = namedQuery;
}
public String getNativeQuery() {
return nativeQuery;
}
/**
* To use a custom native query. You may want to use the option resultClass also when using native queries.
*/
public void setNativeQuery(String nativeQuery) {
this.nativeQuery = nativeQuery;
}
public LockModeType getLockModeType() {
return lockModeType;
}
/**
* To configure the lock mode on the consumer.
*/
public void setLockModeType(LockModeType lockModeType) {
this.lockModeType = lockModeType;
}
public Map<String, Object> getParameters() {
return parameters;
}
/**
* <p>This key/value mapping is used for building the query parameters.
* It is expected to be of the generic type java.util.Map<String, Object> where the keys are the named parameters
* of a given JPA query and the values are their corresponding effective values you want to select for.</p>
* <p>When it's used for producer, Simple expression can be used as a parameter value. It allows you to
* retrieve parameter values from the message body, header and etc.</p>
*/
public void setParameters(Map<String, Object> parameters) {
this.parameters = parameters;
}
public Class<?> getResultClass() {
return resultClass;
}
/**
* Defines the type of the returned payload (we will call entityManager.createNativeQuery(nativeQuery, resultClass)
* instead of entityManager.createNativeQuery(nativeQuery)). Without this option, we will return an object array.
* Only has an affect when using in conjunction with native query when consuming data.
*/
public void setResultClass(Class<?> resultClass) {
this.resultClass = resultClass;
}
public boolean isTransacted() {
return transacted;
}
/**
* Whether to run the consumer in transacted mode, by which all messages will either commit or rollback,
* when the entire batch has been processed. The default behavior (false) is to commit all the previously
* successfully processed messages, and only rollback the last failed message.
*/
public void setTransacted(boolean transacted) {
this.transacted = transacted;
}
public boolean isSkipLockedEntity() {
return skipLockedEntity;
}
/**
* To configure whether to use NOWAIT on lock and silently skip the entity.
*/
public void setSkipLockedEntity(boolean skipLockedEntity) {
this.skipLockedEntity = skipLockedEntity;
}
public DeleteHandler<Object> getDeleteHandler() {
return deleteHandler;
}
/**
* To use a custom DeleteHandler to delete the row after the consumer is done processing the exchange
*/
public void setDeleteHandler(DeleteHandler<Object> deleteHandler) {
this.deleteHandler = deleteHandler;
}
public DeleteHandler<Object> getPreDeleteHandler() {
return preDeleteHandler;
}
/**
* To use a custom Pre-DeleteHandler to delete the row after the consumer has read the entity.
*/
public void setPreDeleteHandler(DeleteHandler<Object> preDeleteHandler) {
this.preDeleteHandler = preDeleteHandler;
}
public Boolean isUseExecuteUpdate() {
return useExecuteUpdate;
}
/**
* To configure whether to use executeUpdate() when producer executes a query.
* When you use INSERT, UPDATE or DELETE statement as a named query, you need to specify
* this option to 'true'.
*/
public void setUseExecuteUpdate(Boolean useExecuteUpdate) {
this.useExecuteUpdate = useExecuteUpdate;
}
// Implementation methods
// -------------------------------------------------------------------------
protected void validate() {
ObjectHelper.notNull(getEntityManagerFactory(), "entityManagerFactory");
}
protected EntityManagerFactory createEntityManagerFactory() {
LocalEntityManagerFactoryBean emfBean = new LocalEntityManagerFactoryBean();
emfBean.setPersistenceUnitName(persistenceUnit);
emfBean.setJpaPropertyMap(getEntityManagerProperties());
emfBean.afterPropertiesSet();
return emfBean.getObject();
}
protected PlatformTransactionManager createTransactionManager() {
JpaTransactionManager tm = new JpaTransactionManager(getEntityManagerFactory());
tm.afterPropertiesSet();
return tm;
}
/**
* @deprecated use {@link #getEntityManagerFactory()} to get hold of factory and create an entity manager using the factory.
*/
@Deprecated
protected EntityManager createEntityManager() {
if (sharedEntityManager) {
return SharedEntityManagerCreator.createSharedEntityManager(getEntityManagerFactory());
} else {
return getEntityManagerFactory().createEntityManager();
}
}
protected TransactionTemplate createTransactionTemplate() {
TransactionTemplate transactionTemplate = new TransactionTemplate(getTransactionManager());
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
transactionTemplate.afterPropertiesSet();
return transactionTemplate;
}
protected Expression createProducerExpression() {
return new ExpressionAdapter() {
public Object evaluate(Exchange exchange) {
Object answer;
// must have a body
try {
if (getEntityType() == null) {
answer = exchange.getIn().getMandatoryBody();
} else {
answer = exchange.getIn().getMandatoryBody(getEntityType());
}
} catch (InvalidPayloadException e) {
throw new InvalidPayloadRuntimeException(exchange, getEntityType(), e.getCause());
}
// is never null
return answer;
}
};
}
}