I have a Kafka spring application with a read-process-write pattern. I want to make sure that the Kafka transaction rolls back if there are any producer errors so that the record is re-consumed using a SeekToCurrentErrorHandler. The default behavior seems to be to log the producer error and continue processing/commit. To override this default behaviour, i have implemented a ProducerListener that throws an exception in the onError method. Is this the recommended approach for ensuring a rollback and the intent behind spring providing us with the listener hook?
Logs showing an exception followed by a commit (The exception didnt result in a rollback)
2020-04-02 18:20:18.314|[ org.springframework.kafka.KafkaListenerEndpointContainer#0-0-C-1]| o.s.kafka.core.KafkaTemplate|TRACE| 456| d3410ae8-c964-41e7-98be-6706a6f2b3b2| Sending: ProducerRecord
2020-04-02 18:20:18.345|[ kafka-producer-network-thread | producer-13]| org.apache.kafka.clients.Metadata|ERROR| | | [Producer clientId=producer-13, transactionalId=tx-0] Topic authorization failed for topics
2020-04-02 18:20:18.354|[ org.springframework.kafka.KafkaListenerEndpointContainer#0-0-C-1]| o.s.k.s.LoggingProducerListener|ERROR| 456| d3410ae8-c964-41e7-98be-6706a6f2b3b2| Exception thrown when sending a message with key='170854907' org.apache.kafka.common.errors.TopicAuthorizationException: Not authorized to access topics: [loyalty-retail-outlet-trans-resp-dev1]
2020-04-02 18:20:18.367|[ org.springframework.kafka.KafkaListenerEndpointContainer#0-0-C-1]| o.s.k.l.KafkaMessageListenerContainer$ListenerConsumer|INFO | | | Sending offsets to transaction: {loyalty-retail-outlet-earn-req-dev-5=OffsetAndMetadata{offset=2220, leaderEpoch=null, metadata=''}}
2020-04-02 18:20:18.368|[ org.springframework.kafka.KafkaListenerEndpointContainer#0-0-C-1]| o.s.k.c.DefaultKafkaProducerFactory|TRACE| | | CloseSafeProducer [delegate=org.apache.kafka.clients.producer.KafkaProducer#5abb103c, txId=tx-0] sendOffsetsToTransaction({loyalty-retail-outlet-earn-req-dev-5=OffsetAndMetadata{offset=2220, leaderEpoch=null, metadata=''}}, earn-unit-test)
2020-04-02 18:20:18.769|[ org.springframework.kafka.KafkaListenerEndpointContainer#0-0-C-1]| o.s.k.t.KafkaTransactionManager|DEBUG| | | Initiating transaction commit
2020-04-02 18:20:18.769|[ org.springframework.kafka.KafkaListenerEndpointContainer#0-0-C-1]| o.s.k.c.DefaultKafkaProducerFactory|DEBUG| | | CloseSafeProducer [delegate=org.apache.kafka.clients.producer.KafkaProducer#5abb103c, txId=tx-0] commitTransaction()
2020-04-02 18:20:18.816|[ org.springframework.kafka.KafkaListenerEndpointContainer#0-0-C-1]| o.s.k.c.DefaultKafkaProducerFactory|TRACE| | | CloseSafeProducer [delegate=org.apache.kafka.clients.producer.KafkaProducer#5abb103c, txId=tx-0] close(PT5S)
The records are produced within a Kafka listener using Kafka Template (read-process-write pattern).
Kafka Template config
#Bean
public KafkaTemplate<Integer, TransactionResponse> kafkaTemplate(
ProducerFactory<Integer, TransactionResponse> producerFactory
, ProducerListener<Integer, TransactionResponse> producerListener) {
KafkaTemplate<Integer, TransactionResponse> kafkaTemplate = new KafkaTemplate<>(producerFactory);
// kafkaTemplate.setProducerListener(producerListener);
return kafkaTemplate;
}
application.properties
spring:
kafka:
producer:
transaction-id-prefix: tx-
acks: all
key-serializer: org.apache.kafka.common.serialization.IntegerSerializer
value-serializer: io.confluent.kafka.serializers.KafkaAvroSerializer
properties:
enable.idempotence: true
delivery.timeout.ms: 180000
listener
#KafkaListener(topics = "${earn.request.topic}", clientIdPrefix = "EarnConsumer", containerFactory = "earnListenerContainerFactory")
public void listen(List<TransactionRequest> requestList,
#Header(KafkaHeaders.GROUP_ID) String groupId,
#Header(KafkaHeaders.RECEIVED_PARTITION_ID) int partitions,
#Header(KafkaHeaders.OFFSET) String offsets,
#Header(KafkaHeaders.RECEIVED_TOPIC) String topic) {
send response method (executes within the listener code)
public void sendResponse(TransactionResponse transactionResponse) {
kafkaTemplate.send(earnResponseTopic, transactionResponse.getEventSummary().getMemberId(), transactionResponse);
}
container config
#Bean
public ConcurrentKafkaListenerContainerFactory<Integer, EarnTransactionRequest> earnListenerContainerFactory(
ConsumerFactory<Integer, EarnTransactionRequest> consumerFactory
, SeekToCurrentBatchErrorHandler seekToCurrentBatchErrorHandler
, KafkaTransactionManager ktm
) {
ConcurrentKafkaListenerContainerFactory<Integer, EarnTransactionRequest> containerFactory = new ConcurrentKafkaListenerContainerFactory<>();
containerFactory.setConsumerFactory(consumerFactory);
containerFactory.setBatchListener(true);
containerFactory.setBatchErrorHandler(seekToCurrentBatchErrorHandler);
containerFactory.setConcurrency(numConcurrentConsumers);
containerFactory.getContainerProperties().setTransactionManager(ktm);
containerFactory.getContainerProperties().setAckOnError(false);
containerFactory.getContainerProperties().setAckMode(ContainerProperties.AckMode.BATCH);
containerFactory.getContainerProperties().setCommitLogLevel(LogIfLevelEnabled.Level.INFO);
containerFactory.getContainerProperties().setLogContainerConfig(true);
containerFactory.getContainerProperties().setMissingTopicsFatal(true);
return containerFactory;
}
EDIT: Simplified application
#Component
public class QuickTest {
private final String responseTopic;
private final KafkaTemplate<Integer, TransactionResponse> kafkaTemplate;
public QuickTest(#Value("${response.topic}") String responseTopic
, KafkaTemplate<Integer, TransactionResponse> kafkaTemplate) {
this.responseTopic = responseTopic;
this.kafkaTemplate = kafkaTemplate;
}
#KafkaListener(topics = "${request.topic}", clientIdPrefix = "Consumer")
public void listen(TransactionRequest requestList) {
kafkaTemplate.send(responseTopic, 123456789, null);
}
}
Logs from start of one transaction to other
2020-04-03 19:04:54.901|[ org.springframework.kafka.KafkaListenerEndpointContainer#0-0-C-1]| o.s.k.l.KafkaMessageListenerContainer$ListenerConsumer|TRACE|Processing ConsumerRecord(topic = req-dev, partition = 1, leaderEpoch = 4, offset = 2185, CreateTime = 1585642853682, serialized key size = -1, serialized value size = 184, headers = RecordHeaders(headers = [], isReadOnly = false), key = null, value = {})
2020-04-03 19:04:54.901|[ org.springframework.kafka.KafkaListenerEndpointContainer#0-0-C-1]| o.s.k.t.KafkaTransactionManager|DEBUG|Creating new transaction with name [null]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2020-04-03 19:04:54.901|[ org.springframework.kafka.KafkaListenerEndpointContainer#0-0-C-1]| o.s.k.c.DefaultKafkaProducerFactory|DEBUG|CloseSafeProducer [delegate=org.apache.kafka.clients.producer.KafkaProducer#43926a5b, txId=transactionx-g21.req-dev.1] beginTransaction()
2020-04-03 19:04:54.901|[ org.springframework.kafka.KafkaListenerEndpointContainer#0-0-C-1]| o.s.k.t.KafkaTransactionManager|DEBUG|Created Kafka transaction on producer [CloseSafeProducer [delegate=org.apache.kafka.clients.producer.KafkaProducer#43926a5b, txId=transactionx-g21.req-dev.1]]
2020-04-03 19:04:54.902|[ org.springframework.kafka.KafkaListenerEndpointContainer#0-0-C-1]| o.s.k.l.a.RecordMessagingMessageListenerAdapter|DEBUG|Processing [GenericMessage [payload={"eventSummary": {"eventId": "102"}}]]
2020-04-03 19:04:54.902|[ org.springframework.kafka.KafkaListenerEndpointContainer#0-0-C-1]| o.s.kafka.core.KafkaTemplate|TRACE|Sending: ProducerRecord(topic= resp-test, partition=null, headers=RecordHeaders(headers = [], isReadOnly = false), key=123456789, value=null, timestamp=null)
2020-04-03 19:04:54.902|[ org.springframework.kafka.KafkaListenerEndpointContainer#0-0-C-1]| o.s.k.c.DefaultKafkaProducerFactory|TRACE|CloseSafeProducer [delegate=org.apache.kafka.clients.producer.KafkaProducer#43926a5b, txId=transactionx-g21.req-dev.1] send(ProducerRecord(topic= resp-test, partition=null, headers=RecordHeaders(headers = [], isReadOnly = false), key=123456789, value=null, timestamp=null))
2020-04-03 19:04:54.928|[ kafka-producer-network-thread | producer-8]| o.apache.kafka.clients.NetworkClient|WARN |[Producer clientId=producer-8, transactionalId=transactionx-g21.req-dev.1] Error while fetching metadata with correlation id 22 : { resp-test=TOPIC_AUTHORIZATION_FAILED}
2020-04-03 19:04:54.928|[ kafka-producer-network-thread | producer-8]| org.apache.kafka.clients.Metadata|ERROR|[Producer clientId=producer-8, transactionalId=transactionx-g21.req-dev.1] Topic authorization failed for topics [ resp-test]
2020-04-03 19:04:54.928|[ org.springframework.kafka.KafkaListenerEndpointContainer#0-0-C-1]| o.s.k.s.LoggingProducerListener|ERROR|Exception thrown when sending a message with key='123456789' and payload='null' to topic resp-test:
org.apache.kafka.common.errors.TopicAuthorizationException: Not authorized to access topics: [ resp-test]
2020-04-03 19:04:54.928|[ org.springframework.kafka.KafkaListenerEndpointContainer#0-0-C-1]| o.s.kafka.core.KafkaTemplate|DEBUG|Failed to send: ProducerRecord(topic= resp-test, partition=null, headers=RecordHeaders(headers = [], isReadOnly = false), key=123456789, value=null, timestamp=null)
org.apache.kafka.common.errors.TopicAuthorizationException: Not authorized to access topics: [ resp-test]
2020-04-03 19:04:54.928|[ org.springframework.kafka.KafkaListenerEndpointContainer#0-0-C-1]| o.s.kafka.core.KafkaTemplate|TRACE|Sent: ProducerRecord(topic= resp-test, partition=null, headers=RecordHeaders(headers = [], isReadOnly = false), key=123456789, value=null, timestamp=null)
2020-04-03 19:04:54.928|[ org.springframework.kafka.KafkaListenerEndpointContainer#0-0-C-1]| o.s.k.l.KafkaMessageListenerContainer$ListenerConsumer|TRACE|Ack: ConsumerRecord(topic = req-dev, partition = 1, leaderEpoch = 4, offset = 2185, CreateTime = 1585642853682, serialized key size = -1, serialized value size = 184, headers = RecordHeaders(headers = [], isReadOnly = false), key = null, value = {"eventSummary": {"eventId": "102"}})
2020-04-03 19:04:54.928|[ org.springframework.kafka.KafkaListenerEndpointContainer#0-0-C-1]| o.s.k.l.KafkaMessageListenerContainer$ListenerConsumer|DEBUG|Sending offsets to transaction: {req-dev-1=OffsetAndMetadata{offset=2186, leaderEpoch=null, metadata=''}}
2020-04-03 19:04:54.928|[ org.springframework.kafka.KafkaListenerEndpointContainer#0-0-C-1]| o.s.k.c.DefaultKafkaProducerFactory|TRACE|CloseSafeProducer [delegate=org.apache.kafka.clients.producer.KafkaProducer#43926a5b, txId=transactionx-g21.req-dev.1] sendOffsetsToTransaction({req-dev-1=OffsetAndMetadata{offset=2186, leaderEpoch=null, metadata=''}}, g21)
2020-04-03 19:04:55.043|[ org.springframework.kafka.KafkaListenerEndpointContainer#0-0-C-1]| o.s.k.t.KafkaTransactionManager|DEBUG|Initiating transaction commit
2020-04-03 19:04:55.043|[ org.springframework.kafka.KafkaListenerEndpointContainer#0-0-C-1]| o.s.k.c.DefaultKafkaProducerFactory|DEBUG|CloseSafeProducer [delegate=org.apache.kafka.clients.producer.KafkaProducer#43926a5b, txId=transactionx-g21.req-dev.1] commitTransaction()
2020-04-03 19:04:55.090|[ org.springframework.kafka.KafkaListenerEndpointContainer#0-0-C-1]| o.s.k.c.DefaultKafkaProducerFactory|TRACE|CloseSafeProducer [delegate=org.apache.kafka.clients.producer.KafkaProducer#43926a5b, txId=transactionx-g21.req-dev.1] close(PT5S)
2020-04-03 19:04:55.091|[ org.springframework.kafka.KafkaListenerEndpointContainer#0-0-C-1]| o.s.k.l.KafkaMessageListenerContainer$ListenerConsumer|TRACE|Processing ConsumerRecord(topic = req-dev, partition = 1, leaderEpoch = 4, offset = 2186, CreateTime = 1585644055280, serialized key size = -1, serialized value size = 184, headers = RecordHeaders(headers = [], isReadOnly = false), key = null, value = {"eventSummary": {"eventId": "104"})
2020-04-03 19:04:55.091|[ org.springframework.kafka.KafkaListenerEndpointContainer#0-0-C-1]| o.s.k.t.KafkaTransactionManager|DEBUG|Creating new transaction with name [null]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2020-04-03 19:04:55.091|[ org.springframework.kafka.KafkaListenerEndpointContainer#0-0-C-1]| o.s.k.c.DefaultKafkaProducerFactory|DEBUG|CloseSafeProducer [delegate=org.apache.kafka.clients.producer.KafkaProducer#43926a5b, txId=transactionx-g21.req-dev.1] beginTransaction()
The error handler runs within the transaction. You should leave it null and the AfterRolllbackProcessor will reseek the records. See the Transactions chapter in the reference manual.
The container needs a KafkaTransactionManager.
See Transactions and After-Rollback Processor.
You should not need to do anything in a ProducerListener.
EDIT
I added authorization configuration to get a TopicAuthorizationException and everything worked as I would have expected (the commit fails)...
#KafkaListener(id = "ktest24", topics = "ktest24")
public void listen(String in) {
System.out.println("1:" + in);
this.template.send("ktest24-out", "sendInTx");
}
1:foo
2020-04-03 14:10:26.619 ERROR 75695 --- [est24.ktest24.0] o.s.k.support.LoggingProducerListener
: Exception thrown when sending a message with key='null' and payload='sendInTx' to topic ktest24-out:
org.apache.kafka.common.errors.TopicAuthorizationException: Not authorized to access topics: [ktest24-out]
2020-04-03 14:10:26.619 ERROR 75695 --- [ ktest24-0-C-1] essageListenerContainer$ListenerConsumer
: Send offsets to transaction failed
org.apache.kafka.common.errors.TopicAuthorizationException: Not authorized to access topics: [ktest24-out]
2020-04-03 14:10:26.620 ERROR 75695 --- [ ktest24-0-C-1] o.s.k.core.DefaultKafkaProducerFactory
: commitTransaction failed: CloseSafeProducer [delegate=org.apache.kafka.clients.producer.KafkaProducer#84412c5, txId=tx-ktest24.ktest24.0]
2020-04-03 14:10:31.627 ERROR 75695 --- [ ktest24-0-C-1] essageListenerContainer$ListenerConsumer
: Transaction rolled back
1:foo
...
Related
I have below executeQuery function that intermittently causes "javax.net.ssl.SSLException: Socket closed" not sure what could be the reason. One possibility I feel is the no of API requests coming around the same time. It's not significantly higher, based on Hikari connection pool I see only 3 connections used max. Any idea?
public Map<String, Object> executeQuery(String graphApiURL, String query){
if(this.client == null){
// this.client = HttpClients.createDefault();
this.client = HttpClientBuilder.create().setDefaultRequestConfig(setRequestConfig()).disableAutomaticRetries().build();
}
try {
log.debug("Execute query url: {}" , graphApiURL);
log.debug("Execute query content: {}" , query);
StringBuffer url = new StringBuffer(graphApiURL);
HttpPost httpPost = new HttpPost(url.toString());
httpPost.setConfig(setRequestConfig());
log.debug("Http post connectTimeout value is : {}", httpPost.getConfig().getConnectTimeout());
log.debug("Http post socket timeout value is : {}", httpPost.getConfig().getSocketTimeout());
log.debug("Http post connection timeout value is : {}", httpPost.getConfig().getConnectionRequestTimeout());
httpPost.setHeader("Authentication", Token);
String postStringEntity = basePayload.replace("##query##", query);
log.debug("Execute full query content: {}" , postStringEntity);
HttpEntity stringEntity = new StringEntity(postStringEntity, ContentType.APPLICATION_JSON);
httpPost.setEntity(stringEntity);
log.debug("Execute http post");
CloseableHttpResponse response = this.client.execute(httpPost);
HttpEntity entity = response.getEntity();
if (entity == null){
log.error("Failed to get response from this endpoint {}.", graphApiURL);
throw new RuntimeException("Failed to get response from this endpoint.");
}
String json = EntityUtils.toString(entity);
log.debug("Http post executed");
log.debug("Close http client");
this.client.close();
if (response != null) {
log.debug("No exceptions encountered in executeQuery with response return code {}", response.getStatusLine().getStatusCode());
}
this.client = null;
Gson gson = new Gson();
if (json.startsWith("{")){
log.debug("Json: {}", json);
return gson.fromJson(json, Map.class);
}else{
log.error("Failed to parse json: {}", json);
throw new RuntimeException("Failed to parse json: " + json);
}
}catch (IOException e){
log.error("Failed to execute query to service API", e);
throw new RuntimeException("Failed to execute query to service API.");
}
}
}
2022-07-19 07:01:46.027 ERROR 13253 --- [http-nio-8081-exec-7][F7EA47757E6248B2AB2665E6EF5AF062] c.a.a.astronomer.GraphHttpClient : Failed to execute query to service API
javax.net.ssl.SSLException: Socket closed
at sun.security.ssl.Alert.createSSLException(Alert.java:127)
at sun.security.ssl.TransportContext.fatal(TransportContext.java:324)
at sun.security.ssl.TransportContext.fatal(TransportContext.java:267)
at sun.security.ssl.TransportContext.fatal(TransportContext.java:262)
at sun.security.ssl.SSLSocketImpl.handleException(SSLSocketImpl.java:1568)
at sun.security.ssl.SSLSocketImpl.access$400(SSLSocketImpl.java:73)
at sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.java:978)
at org.apache.http.impl.io.SessionInputBufferImpl.streamRead(SessionInputBufferImpl.java:137)
at org.apache.http.impl.io.SessionInputBufferImpl.fillBuffer(SessionInputBufferImpl.java:153)
at org.apache.http.impl.io.SessionInputBufferImpl.readLine(SessionInputBufferImpl.java:280)
at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:138)
at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:56)
at org.apache.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:259)
at org.apache.http.impl.DefaultBHttpClientConnection.receiveResponseHeader(DefaultBHttpClientConnection.java:163)
at org.apache.http.impl.conn.CPoolProxy.receiveResponseHeader(CPoolProxy.java:157)
at org.apache.http.protocol.HttpRequestExecutor.doReceiveResponse(HttpRequestExecutor.java:273)
at org.apache.http.protocol.HttpRequestExecutor.execute(HttpRequestExecutor.java:125)
at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:272)
at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:186)
at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:108)
at com.adobe.afdelivery.astronomer.GraphHttpClient.executeQuery(GraphHttpClient.java:187)
at
This intermittent error happens once or twice a day. Appreciate your suggestions if any.
I am having a hard time getting this to work, so maybe you can help me out here.
Technologies used
Xamarin.Forms
Azure App Service
Azure SQL
AzureMobileServices (MobileServiceClient, MobileServer)
Entity Framework Code First
Introduction
My Xamarin.Forms app is used by users with both a billing and a delivery address. The database scheme looks like this:
Users
Id
BillingAddress_Id
DeliveryAddress_Id
Addresses
Id
User_Id
When adding new addresses, I basically do something like that:
var user = await userService.GetUserByIdAsync(...);
var billingAddress = new Address
{
UserId = user.Id,
…
};
var deliveryAddress = new Address
{
UserId = user.Id,
…
}
user.BillingAddress = billingAddress;
user.DeliveryAddress = deliveryAddress;
await addressService.AddNewAddressAsync(billingAddress);
await addressService.AddNewAddressAsync(deliveryAddress);
await userService.UpdateUser(user);
AddNewAddressAsync is a service method that eventually makes the underlying repository create a new address like this:
public async Task<Address> CreateAsync(Address item)
{
await Addresses.InsertAsync(item);
return item;
}
With Addresses being an instance of the IMobileServiceTable interface instantiated like this:
private IMobileServiceTable<Address> Addresses
{
get
{
var table = App.Client.GetTable<Address>();
table.MobileServiceClient.SerializerSettings.ReferenceLoopHandling =
Newtonsoft.Json.ReferenceLoopHandling.Ignore;
return table;
}
}
UpdateUser on the other hand is supposed to patch the existing user by triggering the underlying repository like this:
public async Task<User> PatchAsync(User item)
{
await Users.UpdateAsync(item);
return item;
}
With Users also being an instance of the IMobileServiceTable interface instantiated the very same way as Addresses before:
private IMobileServiceTable<User> Users
{
get
{
var table = App.Client.GetTable<User>();
table.MobileServiceClient.SerializerSettings.PreserveReferencesHandling =
Newtonsoft.Json.PreserveReferencesHandling.Objects;
return table;
}
}
On server side, this is what the controllers do:
AddressController
[Authorize]
public class AddressController : TableController<Address>
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
var context = new AppContext();
DomainManager = new EntityDomainManager<Address>(context, Request);
}
public async Task<IHttpActionResult> PostAddress(Address address)
{
Address current = await InsertAsync(address);
return CreatedAtRoute("Tables", new { id = current.Id }, current);
}
...
}
UserController
[Authorize]
public class UserController : TableController<User>
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
var context = new AppContext();
DomainManager = new EntityDomainManager<User>(context, Request);
}
public Task<User> PatchUser(string id, Delta<User> patch)
{
return UpdateAsync(id, patch);
}
…
}
Here is what is logged when executing the above code:
Insertion of the first address
Request: POST http://localhost:51546/tables/Address
{"$id":"1","id":null,...,"UserId":"8953d3deb9b2459796aa00f43d7416cb","User":null}
iisexpress.exe Information: 0 : Request, Method=POST, Url=http://localhost:51546/tables/Address, Message='http://localhost:51546/tables/Address'
...
iisexpress.exe Information: 0 : Message='Model state is valid. Values: address=SpenceAppService.DataObjects.Address', Operation=HttpActionBinding.ExecuteBindingAsync
...
INSERT [dbo].[Addresses]([Id], [UserId], ...)
VALUES (#0, #1, ...)
SELECT ...
FROM [dbo].[Addresses]
WHERE ##ROWCOUNT > 0 AND [Id] = #0
-- #0: '2a407222f6984052b90d233fa9935286' (Type = String, Size = 128)
-- #1: '8953d3deb9b2459796aa00f43d7416cb' (Type = String, Size = 128)
...
-- Executing asynchronously at 23.05.2019 13:22:10 +02:00
-- Completed in 7 ms with result: SqlDataReader
Committed transaction at 23.05.2019 13:22:11 +02:00
Closed connection at 23.05.2019 13:22:11 +02:00
...
iisexpress.exe Information: 0 : Response, Status=201 (Created), Method=POST, Url=http://localhost:51546/tables/Address, Message='Content-type='application/json; charset=utf-8', content-length=unknown'
...
Response: Created
{"userId":"8953d3deb9b2459796aa00f43d7416cb",...,"id":"2a407222f6984052b90d233fa9935286",...}
Insertion of the second address
Request: POST http://localhost:51546/tables/Address
{"$id":"1","id":null,...,"UserId":"8953d3deb9b2459796aa00f43d7416cb","User":null}
iisexpress.exe Information: 0 : Request, Method=POST, Url=http://localhost:51546/tables/Address, Message='http://localhost:51546/tables/Address'
...
iisexpress.exe Information: 0 : Message='Model state is valid. Values: address=SpenceAppService.DataObjects.Address', Operation=HttpActionBinding.ExecuteBindingAsync
...
INSERT [dbo].[Addresses]([Id], [UserId], ...)
VALUES (#0, #1, ...)
SELECT ...
FROM [dbo].[Addresses]
WHERE ##ROWCOUNT > 0 AND [Id] = #0
-- #0: 'a56e1ca3a7b341a39bc00d22772e39e5' (Type = String, Size = 128)
-- #1: '8953d3deb9b2459796aa00f43d7416cb' (Type = String, Size = 128)
...
-- Executing asynchronously at 23.05.2019 13:22:11 +02:00
-- Completed in 0 ms with result: SqlDataReader
Committed transaction at 23.05.2019 13:22:11 +02:00
Closed connection at 23.05.2019 13:22:11 +02:00
...
iisexpress.exe Information: 0 : Response, Status=201 (Created), Method=POST, Url=http://localhost:51546/tables/Address, Message='Content-type='application/json; charset=utf-8', content-length=unknown'
...
Response: Created
{"userId":"8953d3deb9b2459796aa00f43d7416cb",...,"id":"a56e1ca3a7b341a39bc00d22772e39e5",...}
Patch of the user
Request: PATCH http://localhost:51546/tables/User/8953d3deb9b2459796aa00f43d7416cb
{"$id":"1","id":"8953d3deb9b2459796aa00f43d7416cb",...,"BillingAddress":{"$id":"7","id":"2a407222f6984052b90d233fa9935286",...,"UserId":"8953d3deb9b2459796aa00f43d7416cb","User":null},"DeliveryAddress":{"$id":"8","id":"a56e1ca3a7b341a39bc00d22772e39e5",...,"UserId":"8953d3deb9b2459796aa00f43d7416cb","User":null}}
iisexpress.exe Information: 0 : Request, Method=PATCH, Url=http://localhost:51546/tables/User/8953d3deb9b2459796aa00f43d7416cb, Message='http://localhost:51546/tables/User/8953d3deb9b2459796aa00f43d7416cb'
...
iisexpress.exe Information: 0 : Message='Model state is valid. Values: id=8953d3deb9b2459796aa00f43d7416cb, patch=System.Web.Http.OData.Delta`1[SpenceAppService.DataObjects.User]', Operation=HttpActionBinding.ExecuteBindingAsync
...
Opened connection asynchronously at 23.05.2019 13:22:30 +02:00
SELECT
...
[Limit1].[Id] AS [Id],
...
[Limit1].[BillingAddress_Id] AS [BillingAddress_Id],
[Limit1].[DeliveryAddress_Id] AS [DeliveryAddress_Id]
FROM ( SELECT TOP (1)
[Extent1].[Id] AS [Id],
...
[Extent1].[BillingAddress_Id] AS [BillingAddress_Id],
[Extent1].[DeliveryAddress_Id] AS [DeliveryAddress_Id]
FROM [dbo].[Users] AS [Extent1]
WHERE [Extent1].[Id] = #p__linq__0
) AS [Limit1]
-- p__linq__0: '8953d3deb9b2459796aa00f43d7416cb' (Type = String, Size = 4000)
...
INSERT [dbo].[Addresses]([Id], [UserId], ...)
VALUES (#0, #1, ...)
SELECT ...
FROM [dbo].[Addresses]
WHERE ##ROWCOUNT > 0 AND [Id] = #0
-- #0: '2a407222f6984052b90d233fa9935286' (Type = String, Size = 128)
-- #1: '8953d3deb9b2459796aa00f43d7416cb' (Type = String, Size = 128)
...
-- Executing asynchronously at 23.05.2019 13:22:30 +02:00
-- Failed in 1 ms with error: Violation of PRIMARY KEY constraint 'PK_dbo.Addresses'. Cannot insert duplicate key in object 'dbo.Addresses'. The duplicate key value is (2a407222f6984052b90d233fa9935286).
The statement has been terminated.
...
The request could not be completed. (Conflict)
Problem
As you can see, while patching the user, the underlying method tries to also insert the addresses that actually have already been inserted, so the error message does perfectly make sense.
However, how do I get this to work?
When I don't insert the addresses first, they don't get ids from the server, so when the userService patches the user which in turn tries to insert the addresses (which then don't exist yet), I get an error message telling me, that the Id field (of the address) is required.
When I add Id fields for both BillingAddress and DeliveryAddress to the user object and skip setting the address fields before updating the user but set their respective ids instead (after inserting the addresses), everything works as expected.
However, I still seem to be missing the üoint, because I thought, Entity Framework should be able to handle all of this internally.
I do know, that working with Azure Appservice, Entity Framework, Code First, an ASP.NET MVC backend and dependent tables accessed by using the Azure Mobile Apps Client and Server API makes things different,... but I was not able to find samples that clearly show how to get this just right...
I also read about having to configure relations either by using fluent API or by decorating properties. However, even when doing nothing, it seems to me as if the framework is able to handle this by itself. For instance when adding the BillingAddressId property to the user data object on server side and executing an Add-Migration, the migration code contains foreign key constraints without me having to explicitly configure them:
public override void Up()
{
AddColumn("dbo.Users", "BillingAddressId", c => c.String(maxLength: 128));
AddColumn("dbo.Users", "DeliveryAddressId", c => c.String(maxLength: 128));
CreateIndex("dbo.Users", "BillingAddressId");
CreateIndex("dbo.Users", "DeliveryAddressId");
AddForeignKey("dbo.Users", "BillingAddressId", "dbo.Addresses", "Id");
AddForeignKey("dbo.Users", "DeliveryAddressId", "dbo.Addresses", "Id");
}
You're User entities aren't aware of the Address objects that were added to the DB. What you want to do is var entity = context.Users.Find(id); and update entity with what's in patch using either entity.Prop = patch.Prop; or using something like Automapper to update the entity and then call your UpdateAsync. This will make the context aware of the new changes made to your User entity and will have the foreign keys populated.
Hope this helps.
Please help me out in solving the error.
I want to test my Rest Controller using MockMvc but after testing i am getting the above error.
My GetTest is getting successfully run but my POST test is throwing out error. Maybe the way I have written the POST MVC test is not correct. Below is the whole code if need any other information please comment.
#RestController
public class Controller {
#Autowired
StudentRepository studentRepository;
#RequestMapping(method= RequestMethod.POST,value="/students" ,consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public void addTopic(#RequestBody Student student){
studentRepository.addStudent(student);
}
}
The unit test class:
#RunWith(SpringRunner.class)
#WebMvcTest
public class TestingApplicationTests {
#Autowired
private MockMvc mvc;
#MockBean
private StudentRepository studentRepository;
#Test
public void firstTest() throws Exception {
Student stud = new Student("Niladri", "nila", "asda");
Student stud1 = new Student("Abhirup", "abhi", "asda");
Student stud2 = new Student("Satarupa", "sata", "asda");
List<Student> list = Arrays.asList(stud, stud1, stud2);
given(studentRepository.getAll()).willReturn(list);
mvc.perform(get("/students"))
.andExpect(status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$", hasSize(3)));
}
#Test
public void secondTest() throws Exception {
Student student1=new Student("Niladri","Ni","seventy");
String expectedJson=this.mapToJson(student1);
mvc.perform(post("/students").contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON).content(expectedJson))
.andExpect(status().isOk());
}
//Maps an object into a JSON String . Uses a JackSon ObjectMapper
private String mapToJson(Object student1) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(student1);
}
}
My First test is successfully getting completed(i.e get) but my second test(i.e POST) fails with below error:
Below are the logs.
2018-01-19 16:10:22.640 INFO 13348 --- [ main] c.test.Testing.TestingApplicationTests : Starting TestingApplicationTests on 114983-T470p with PID 13348 (started by nchanda in C:\Users\nchanda\Downloads\Testing)
2018-01-19 16:10:22.641 INFO 13348 --- [ main] c.test.Testing.TestingApplicationTests : No active profile set, falling back to default profiles: default
2018-01-19 16:10:22.655 INFO 13348 --- [ main] o.s.w.c.s.GenericWebApplicationContext : Refreshing org.springframework.web.context.support.GenericWebApplicationContext#52719fb6: startup date [Fri Jan 19 16:10:22 IST 2018]; root of context hierarchy
2018-01-19 16:10:23.622 INFO 13348 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for #ControllerAdvice: org.springframework.web.context.support.GenericWebApplicationContext#52719fb6: startup date [Fri Jan 19 16:10:22 IST 2018]; root of context hierarchy
2018-01-19 16:10:23.678 INFO 13348 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/students]}" onto public java.util.List<com.test.Testing.Student> com.test.Testing.Controller.student()
or perhaps need to add/enable type information?)
at [Source: java.io.PushbackInputStream#70242f38; line: 1, column: 2]
MockHttpServletRequest:
HTTP Method = GET
Request URI = /students
Parameters = {}
Headers = {}
Handler:
Type = com.test.Testing.Controller
Method = public java.util.List<com.test.Testing.Student> com.test.Testing.Controller.student()
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = {Content-Type=[application/json;charset=UTF-8]}
Content type = application/json;charset=UTF-8
Body = [{"name":"Niladri","id":"nila","marks":"asda"},{"name":"Abhirup","id":"abhi","marks":"asda"},{"name":"Satarupa","id":"sata","marks":"asda"}]
Forwarded URL = null
Redirected URL = null
Cookies = []
MockHttpServletRequest:
HTTP Method = POST
Request URI = /students
Parameters = {}
Headers = {Content-Type=[application/json], Accept=[application/json]}
Handler:
Type = com.test.Testing.Controller
Method = public void com.test.Testing.Controller.addTopic(com.test.Testing.Student)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.http.converter.HttpMessageNotReadableException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 400
Error message = null
Headers = {}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 1.809 sec <<< FAILURE! - in com.test.Testing.TestingApplicationTests
secondTest(com.test.Testing.TestingApplicationTests) Time elapsed: 0.037 sec <<< FAILURE!
java.lang.AssertionError: Status expected:<200> but was:<400>
Results :
Failed tests:
TestingApplicationTests.secondTest:97 Status expected:<200> but was:<400>
I'm using Spring MVC NamedParameterJdbcTemplate, when i call "userService.addAndSendAccountActivationEmail(user, request)" from controller, i got error. But if i don't use named parameter(like: private static final String ADD = "insert into user_(user_id, email, username, password, create_date, modified_date, status_) values(?, ?, ?, ?, ?, ?, ?)"), everything work fine for me.
When this line has been called on BaseDao "jdbcTemplate.getJdbcOperations().update(sql, param, keyHolder)", i see nothing in "param" object.
I've added "org.springframework.jdbc" level to track, see my error log below.
Any help will be greatly appreciated.
#Service("userService")
public class UserService implements Serializable {
private static final long serialVersionUID = 1L;
#Resource
private UserDao userDao;
#Transactional
public void addAndSendAccountActivationEmail(User user, HttpServletRequest request) throws SystemException {
add(user);
// sendAccountActivationEmail(user, request);
}
}
public class UserDao extends BaseDao {
*/
private static final long serialVersionUID = 1L;
public UserDao(NamedParameterJdbcTemplate jdbcTemplate) {
super(jdbcTemplate);
}
private static final String ADD = "insert into user_(user_id, email, username, password, create_date, modified_date, status_) values(:userId, :email, :username, :password, :createDate, :modifiedDate, :status)";
public void add(User user) throws SystemException {
long userId = updateForLongKey(ADD, user);
user.setUserId(userId);
}
}
public class BaseDao implements Serializable {
private static final long serialVersionUID = 1L;
protected NamedParameterJdbcTemplate jdbcTemplate;
protected BaseDao(NamedParameterJdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
protected <T> long updateForLongKey(final String sql,T t){
SqlParameterSource param = new BeanPropertySqlParameterSource(t);
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.getJdbcOperations().update(sql, param, keyHolder);
return keyHolder.getKey().longValue();
}
}
DEBUG: org.springframework.jdbc.datasource.DataSourceTransactionManager - Creating new transaction with name [com.htws.account.service.UserService.addAndSendAccountActivationEmail]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
DEBUG: org.springframework.jdbc.datasource.DataSourceTransactionManager - Acquired Connection [com.mchange.v2.c3p0.impl.NewProxyConnection#6589127] for JDBC transaction
DEBUG: org.springframework.jdbc.datasource.DataSourceTransactionManager - Switching JDBC Connection [com.mchange.v2.c3p0.impl.NewProxyConnection#6589127] to manual commit
DEBUG: org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL update
DEBUG: org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL statement [insert into user_(user_id, email, username, password, create_date, modified_date, status_) values(:userId, :email, :username, :password, :createDate, :modifiedDate, :status)]
TRACE: org.springframework.jdbc.core.StatementCreatorUtils - Setting SQL statement parameter value: column index 1, parameter value [org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource#1dafb644], value class [org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource], SQL type unknown
INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [org/springframework/jdbc/support/sql-error-codes.xml]
INFO : org.springframework.jdbc.support.SQLErrorCodesFactory - SQLErrorCodes loaded: [DB2, Derby, H2, HSQL, Informix, MS-SQL, MySQL, Oracle, PostgreSQL, Sybase]
DEBUG: org.springframework.jdbc.support.SQLErrorCodesFactory - Looking up default SQLErrorCodes for DataSource [com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 5, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 5000, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, dataSourceName -> 1hge16h8tu21anplw8eof|6469cee6, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, identityToken -> 1hge16h8tu21anplw8eof|6469cee6, idleConnectionTestPeriod -> 3000, initialPoolSize -> 10, jdbcUrl -> jdbc:mysql://localhost:3306/htws?useUnicode=true&characterEncoding=UTF-8, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 1800, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 50, maxStatements -> 0, maxStatementsPerConnection -> 0, minPoolSize -> 10, numHelperThreads -> 3, numThreadsAwaitingCheckoutDefaultUser -> 0, preferredTestQuery -> null, properties -> {user=******, password=******}, propertyCycle -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, usesTraditionalReflectiveProxies -> false ]]
DEBUG: org.springframework.jdbc.support.SQLErrorCodesFactory - Database product name cached for DataSource [com.mchange.v2.c3p0.ComboPooledDataSource#bd9f0537]: name is 'MySQL'
DEBUG: org.springframework.jdbc.support.SQLErrorCodesFactory - SQL error codes for 'MySQL' found
DEBUG: org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator - Unable to translate SQLException with Error code '0', will now try the fallback translator
DEBUG: org.springframework.jdbc.support.SQLStateSQLExceptionTranslator - Extracted SQL state class 'S1' from value 'S1009'
TRACE: org.springframework.jdbc.datasource.DataSourceTransactionManager - Triggering beforeCompletion synchronization
DEBUG: org.springframework.jdbc.datasource.DataSourceTransactionManager - Initiating transaction rollback
DEBUG: org.springframework.jdbc.datasource.DataSourceTransactionManager - Rolling back JDBC transaction on Connection [com.mchange.v2.c3p0.impl.NewProxyConnection#6589127]
TRACE: org.springframework.jdbc.datasource.DataSourceTransactionManager - Triggering afterCompletion synchronization
DEBUG: org.springframework.jdbc.datasource.DataSourceTransactionManager - Releasing JDBC Connection [com.mchange.v2.c3p0.impl.NewProxyConnection#6589127] after transaction
DEBUG: org.springframework.jdbc.datasource.DataSourceUtils - Returning JDBC Connection to DataSource
ERROR: com.htws.account.web.UserController - PreparedStatementCallback; SQL [insert into user_(user_id, email, username, password, create_date, modified_date, status_) values(:userId, :email, :username, :password, :createDate, :modifiedDate, :status)]; Invalid argument value: java.io.NotSerializableException; nested exception is java.sql.SQLException: Invalid argument value: java.io.NotSerializableException
org.springframework.dao.TransientDataAccessResourceException: PreparedStatementCallback; SQL [insert into user_(user_id, email, username, password, create_date, modified_date, status_) values(:userId, :email, :username, :password, :createDate, :modifiedDate, :status)]; Invalid argument value: java.io.NotSerializableException; nested exception is java.sql.SQLException: Invalid argument value: java.io.NotSerializableException
at org.springframework.jdbc.support.SQLStateSQLExceptionTranslator.doTranslate(SQLStateSQLExceptionTranslator.java:107)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:72)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:80)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:80)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:603)
at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:812)
at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:868)
at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:876)
at com.htws.core.dao.BaseDao.updateForLongKey(BaseDao.java:34)
at com.htws.account.dao.UserDao.add(UserDao.java:86)
at com.htws.account.service.UserService.add(UserService.java:65)
at com.htws.account.service.UserService.addAndSendAccountActivationEmail(UserService.java:58)
at com.htws.account.service.UserService$$FastClassByCGLIB$$4b2f4817.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:698)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:631)
at com.htws.account.service.UserService$$EnhancerByCGLIB$$78d9a26a.addAndSendAccountActivationEmail(<generated>)
at com.htws.account.web.UserController.processRegister(UserController.java:60)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:219)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:745)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:686)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:925)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:920)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:827)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:637)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:801)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain(AbstractShiroFilter.java:449)
at org.apache.shiro.web.servlet.AbstractShiroFilter$1.call(AbstractShiroFilter.java:365)
at org.apache.shiro.subject.support.SubjectCallable.doCall(SubjectCallable.java:90)
at org.apache.shiro.subject.support.SubjectCallable.call(SubjectCallable.java:83)
at org.apache.shiro.subject.support.DelegatingSubject.execute(DelegatingSubject.java:383)
at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:362)
at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:259)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at com.springsource.insight.collection.tcserver.request.HttpRequestOperationCollectionValve.invoke(HttpRequestOperationCollectionValve.java:64)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:857)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:588)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:409)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918)
at java.lang.Thread.run(Thread.java:680)
Caused by: java.sql.SQLException: Invalid argument value: java.io.NotSerializableException
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1074)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:988)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:974)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:919)
at com.mysql.jdbc.PreparedStatement.setSerializableObject(PreparedStatement.java:4412)
at com.mysql.jdbc.PreparedStatement.setObject(PreparedStatement.java:4083)
at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.setObject(NewProxyPreparedStatement.java:365)
at org.springframework.jdbc.core.StatementCreatorUtils.setValue(StatementCreatorUtils.java:365)
at org.springframework.jdbc.core.StatementCreatorUtils.setParameterValueInternal(StatementCreatorUtils.java:217)
at org.springframework.jdbc.core.StatementCreatorUtils.setParameterValue(StatementCreatorUtils.java:145)
at org.springframework.jdbc.core.ArgPreparedStatementSetter.doSetValue(ArgPreparedStatementSetter.java:65)
at org.springframework.jdbc.core.ArgPreparedStatementSetter.setValues(ArgPreparedStatementSetter.java:46)
at org.springframework.jdbc.core.JdbcTemplate$2.doInPreparedStatement(JdbcTemplate.java:816)
at org.springframework.jdbc.core.JdbcTemplate$2.doInPreparedStatement(JdbcTemplate.java:812)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:587)
... 63 more
Caused by: java.io.NotSerializableException: org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1164)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:330)
at com.mysql.jdbc.PreparedStatement.setSerializableObject(PreparedStatement.java:4401)
... 73 more
try to use NamedParameterJdbcTemplate instead of SqlParameterSource .
Jdbc template dont have method which uses a SqlParameterSource
I am trying to develop an app for Facebook. I wrote a facebook controller and created connectionFactory object, got the oAuth oprations and accessgrant object. I got the access token also But Once I try to create a connection
Connection<Facebook> connection = connectionFactory.createConnection(accessGrant);
I am getting the error :
MissingAuthorizationException: Authorization is required for the operation, but the API binding was created without authorization.
Full stack trace :
[1/25/12 9:18:17:097 IST] 0000001a SystemOut O oauthOperations :org.springframework.social.facebook.connect.FacebookOAuth2Template#46714671
[1/25/12 9:18:17:097 IST] 0000001a SystemOut O authorizeUrl :https://graph.facebook.com/oauth/authorize?client_id=xxxxx&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A80%2FSpringSocial%2Fstart&scope=read_stream%2Cemail%2Cpublish_stream%2Coffline_access
[1/25/12 9:19:15:930 IST] 0000001a SystemOut O oauthOperations authorizationCode :org.springframework.social.facebook.connect.FacebookOAuth2Template#46714671
[1/25/12 9:19:25:197 IST] 0000001a SystemOut O accssTokentemp :xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
[1/25/12 9:19:35:541 IST] 0000001a SystemOut O facebook :org.springframework.social.facebook.api.impl.FacebookTemplate#21c221c2
[1/25/12 9:19:35:994 IST] 0000001a RestTemplate W org.springframework.web.client.RestTemplate handleResponseError GET request for "https://graph.facebook.com/me" resulted in 400 (Bad Request); invoking error handler
[1/25/12 9:19:36:041 IST] 0000001a servlet E com.ibm.ws.webcontainer.servlet.ServletWrapper service SRVE0068E: Uncaught exception created in one of the service methods of the servlet SpringSocial in application SpringSocialFacebookEar. Exception created : org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.social.MissingAuthorizationException: Authorization is required for the operation, but the API binding was created without authorization.
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:681)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:574)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:718)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:831)
at com.ibm.ws.cache.servlet.ServletWrapper.serviceProxied(ServletWrapper.java:307)
at com.ibm.ws.cache.servlet.CacheHook.handleFragment(CacheHook.java:579)
at com.ibm.ws.cache.servlet.CacheHook.handleServlet(CacheHook.java:250)
at com.ibm.ws.cache.servlet.ServletWrapper.service(ServletWrapper.java:259)
at com.ibm.ws.webcontainer.servlet.ServletWrapper.service(ServletWrapper.java:1663)
at com.ibm.ws.webcontainer.servlet.ServletWrapper.handleRequest(ServletWrapper.java:939)
Caused by: org.springframework.social.MissingAuthorizationException: Authorization is required for the operation, but the API binding was created without authorization.
at org.springframework.social.facebook.api.impl.FacebookErrorHandler.handleFacebookError(FacebookErrorHandler.java:95)
at org.springframework.social.facebook.api.impl.FacebookErrorHandler.handleError(FacebookErrorHandler.java:60)
at org.springframework.web.client.RestTemplate.handleResponseError(RestTemplate.java:486)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:443)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:415)
at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:213)
at org.springframework.social.facebook.api.impl.FacebookTemplate.fetchObject(FacebookTemplate.java:165)
at org.springframework.social.facebook.api.impl.UserTemplate.getUserProfile(UserTemplate.java:50)
at org.springframework.social.facebook.api.impl.UserTemplate.getUserProfile(UserTemplate.java:46)
at org.springframework.social.facebook.connect.FacebookAdapter.setConnectionValues(FacebookAdapter.java:42)
at org.springframework.social.facebook.connect.FacebookAdapter.setConnectionValues(FacebookAdapter.java:30)
at org.springframework.social.connect.support.AbstractConnection.setValues(AbstractConnection.java:172)
at org.springframework.social.connect.support.AbstractConnection.initKey(AbstractConnection.java:135)
at org.springframework.social.connect.support.OAuth2Connection.(OAuth2Connection.java:73)
at org.springframework.social.connect.support.OAuth2ConnectionFactory.createConnection(OAuth2ConnectionFactory.java:58)
at com.tgt.social.controller.FaceBookConnectController.facebookConnect(FaceBookConnectController.java:107)
My Controller class :
#Controller
public class FaceBookConnectController {
private static OAuth2Operations oauthOperations = null;
private static FacebookConnectionFactory connectionFactory = null;
private static OAuth2Parameters params = null;
#RequestMapping("/start")
public ModelAndView facebookConnect(Model model,HttpServletRequest request,HttpServletResponse response) {
Security.setProperty("ssl.SocketFactory.provider", "com.ibm.jsse2.SSLSocketFactoryImpl");
Security.setProperty("ssl.ServerSocketFactory.provider", "com.ibm.jsse2.SSLServerSocketFactoryImpl");
System.setProperty("https.proxyHost", "localhost");
System.setProperty("https.proxyPort", "8888");
String clientId = "xxxxxxxxx";
String clientSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxx";
String redirectUrl = "http://localhost:80/SpringSocial/start";
try {
String authorizationCode = request.getParameter("code");
if(null != authorizationCode){
MultiValueMap paramsMap1 = new LinkedMultiValueMap();
System.out.println("oauthOperations authorizationCode :"+oauthOperations);
//paramsMap.set("client_id", clientId);
//paramsMap.set("client_secret", clientSecret);
//paramsMap.set("code", authorizationCode);
paramsMap1.set("redirect_uri", redirectUrl);
paramsMap1.set("grant_type", "authorization_code");
paramsMap1.set("scope","read_stream,user_about_me,user_birthday,user_likes,user_status,email,publish_stream,offline_access");
//AccessGrant accessGrant = oauthOperations.exchangeForAccess(authorizationCode, redirectUrl, paramsMap1);
System.out.println("connectionFactory authorizationCode :"+connectionFactory);
AccessGrant accessGrant = connectionFactory.getOAuthOperations().exchangeForAccess(authorizationCode, redirectUrl, paramsMap1);
String accssTokentemp = accessGrant.getAccessToken();
System.out.println("accssTokentemp :"+accssTokentemp);
String scope_p = accessGrant.getScope();
System.out.println("scope_p :"+scope_p);
Facebook facebook = new FacebookTemplate(accssTokentemp);
System.out.println("facebook :"+facebook);
Connection<Facebook> connection = connectionFactory.createConnection(accessGrant);
//FacebookProfile userProfile = facebook.userOperations().getUserProfile();
System.out.println("connection :"+connection);
}else{
MultiValueMap paramsMap = new LinkedMultiValueMap();
connectionFactory = new FacebookConnectionFactory(clientId,clientSecret);
System.out.println("connectionFactory :"+connectionFactory);
oauthOperations = connectionFactory.getOAuthOperations();
params = new OAuth2Parameters();
params.setRedirectUri(redirectUrl);
params.setScope("read_stream,user_about_me,user_birthday,user_likes,user_status,email,publish_stream,offline_access");
paramsMap.set("client_id", clientId);
paramsMap.set("client_secret", clientSecret);
paramsMap.set("code", authorizationCode);
paramsMap.set("redirect_uri", redirectUrl);
paramsMap.set("grant_type", "authorization_code");
System.out.println("oauthOperations :"+oauthOperations);
String authorizeUrl = oauthOperations.buildAuthorizeUrl(GrantType.AUTHORIZATION_CODE, params);
System.out.println("authorizeUrl :"+authorizeUrl);
response.sendRedirect(authorizeUrl);
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
Please let me know what is wrong here?
Is the instantiation is not correct for connectionFactory object.
You need to addConnection to the usersConnectionRepository since social tries to locate a user's already established connection if any, and tries to get it authorized and use it