/*
* Copyright (C) 2015 Red Hat, Inc. and/or its affiliates.
*
* Licensed 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.jboss.errai.jpa.sync.rebind;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jboss.errai.codegen.Parameter;
import org.jboss.errai.codegen.Statement;
import org.jboss.errai.codegen.builder.AnonymousClassStructureBuilder;
import org.jboss.errai.codegen.builder.BlockBuilder;
import org.jboss.errai.codegen.exception.GenerationException;
import org.jboss.errai.codegen.meta.MetaClass;
import org.jboss.errai.codegen.meta.MetaClassFactory;
import org.jboss.errai.codegen.meta.MetaField;
import org.jboss.errai.codegen.meta.MetaMethod;
import org.jboss.errai.codegen.meta.MetaParameter;
import org.jboss.errai.codegen.util.GenUtil;
import org.jboss.errai.codegen.util.Refs;
import org.jboss.errai.codegen.util.Stmt;
import org.jboss.errai.ioc.client.api.CodeDecorator;
import org.jboss.errai.ioc.client.container.InitializationCallback;
import org.jboss.errai.ioc.rebind.ioc.bootstrapper.InjectUtil;
import org.jboss.errai.ioc.rebind.ioc.extension.IOCDecoratorExtension;
import org.jboss.errai.ioc.rebind.ioc.injector.api.Decorable;
import org.jboss.errai.ioc.rebind.ioc.injector.api.FactoryController;
import org.jboss.errai.jpa.sync.client.local.ClientSyncWorker;
import org.jboss.errai.jpa.sync.client.local.ClientSyncWorker.QueryParamInitCallback;
import org.jboss.errai.jpa.sync.client.local.DataSyncCallback;
import org.jboss.errai.jpa.sync.client.local.Sync;
import org.jboss.errai.jpa.sync.client.local.SyncParam;
import org.jboss.errai.jpa.sync.client.shared.SyncResponses;
/**
* Generates an {@link InitializationCallback} that contains data sync logic.
*
* @author Christian Sadilek <csadilek@redhat.com>
* @author Jonathan Fuerth <jfuerth@redhat.com>
*/
@CodeDecorator
public class SyncDecorator extends IOCDecoratorExtension<Sync> {
public SyncDecorator(Class<Sync> decoratesWith) {
super(decoratesWith);
}
@Override
public void generateDecorator(final Decorable decorable, final FactoryController controller) {
MetaMethod method = decorable.getAsMethod();
MetaParameter[] params = method.getParameters();
if (params.length != 1 || !params[0].getType().getErased().equals(MetaClassFactory.get(SyncResponses.class))) {
throw new GenerationException("Methods annotated with @" + Sync.class.getName()
+ " need to have exactly one parameter of type: "
+ SyncResponses.class.getName() +
". Check method: " + GenUtil.getMethodString(method) + " in class "
+ method.getDeclaringClass().getFullyQualifiedName());
}
Sync syncAnnotation = (Sync) decorable.getAnnotation();
controller.addInitializationStatements(createInitStatements(decorable.getDecorableDeclaringType(), "obj", syncAnnotation, decorable, controller));
final Statement syncWorker = controller.getReferenceStmt("syncWorker", ClientSyncWorker.class);
final Statement destruction = Stmt.nestedCall(syncWorker).invoke("stop");
controller.addDestructionStatements(Collections.singletonList(destruction));
}
/**
* Generates an anonymous {@link DataSyncCallback} that will invoke the decorated sync method.
*/
private Statement createSyncCallback(Decorable decorable) {
return Stmt.newObject(DataSyncCallback.class)
.extend()
.publicOverridesMethod("onSync", Parameter.of(SyncResponses.class, "responses", true))
.append(decorable.getAccessStatement(Stmt.loadVariable("responses")))
.finish()
.finish();
}
private List<Statement> createInitStatements(final MetaClass type, final String initVar, final Sync syncAnnotation,
final Decorable decorable, final FactoryController controller) {
final List<Statement> statements = new ArrayList<Statement>();
statements.add(Stmt.declareFinalVariable("objectClass", Class.class, Stmt.loadLiteral(Object.class)));
BlockBuilder<AnonymousClassStructureBuilder> queryParamCallback =
Stmt.newObject(QueryParamInitCallback.class).extend().publicOverridesMethod("getQueryParams");
queryParamCallback.append(Stmt.declareFinalVariable("paramsMap", Map.class, Stmt.newObject(HashMap.class)));
for (SyncParam param : syncAnnotation.params()) {
Statement fieldValueStmt;
String val = param.val().trim();
if (val.startsWith("{") && val.endsWith("}")) {
String fieldName = val.substring(1, val.length() - 1);
MetaField field = decorable.getDecorableDeclaringType().getInheritedField(fieldName);
fieldValueStmt =
InjectUtil.getPublicOrPrivateFieldValue(controller, field);
}
else {
fieldValueStmt = Stmt.loadLiteral(val);
}
queryParamCallback.append(Stmt.loadVariable("paramsMap").invoke("put", param.name(), fieldValueStmt));
}
queryParamCallback.append(Stmt.loadVariable("paramsMap").returnValue());
statements.add(Stmt.declareFinalVariable("paramsCallback",
QueryParamInitCallback.class, queryParamCallback.finish().finish()));
statements.add(controller.setReferenceStmt("syncWorker", Stmt.invokeStatic(ClientSyncWorker.class, "create",
syncAnnotation.query(), Stmt.loadVariable("objectClass"), null)));
final Statement syncWorkerRef = controller.getReferenceStmt("syncWorker", ClientSyncWorker.class);
statements.add(
Stmt.nestedCall(syncWorkerRef).invoke("addSyncCallback", createSyncCallback(decorable)));
statements.add(
Stmt.nestedCall(syncWorkerRef).invoke("start", Refs.get("instance"), Stmt.loadVariable("paramsCallback")));
return statements;
}
}