/* AngularBeans, CDI-AngularJS bridge Copyright (c) 2014, Bessem Hmidi. or third-party contributors as indicated by * the @author tags or express copyright attribution statements applied by the authors. This copyrighted material is * made available to anyone wishing to use, modify, copy, or redistribute it subject to the terms and conditions of the * GNU Lesser General Public License, as published by the Free Software Foundation. This program is distributed in the * hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. */ package angularBeans.boot; import static angularBeans.api.http.HttpMethod.DELETE_TO_REPLACE; import static angularBeans.api.http.HttpMethod.GET; import static angularBeans.api.http.HttpMethod.POST; import static angularBeans.api.http.HttpMethod.PUT; import static angularBeans.events.Callback.AFTER_SESSION_READY; import static angularBeans.events.Callback.BEFORE_SESSION_READY; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.util.HashSet; import java.util.Set; import java.util.UUID; import javax.annotation.PostConstruct; import javax.enterprise.context.SessionScoped; import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import angularBeans.api.CORS; import angularBeans.api.Eval; import angularBeans.api.NGPostConstruct; import angularBeans.api.NGReturn; import angularBeans.api.NGSubmit; import angularBeans.api.http.Delete; import angularBeans.api.http.Get; import angularBeans.api.http.HttpMethod; import angularBeans.api.http.Post; import angularBeans.api.http.Put; import angularBeans.context.BeanLocator; import angularBeans.context.NGSessionScopeContext; import angularBeans.events.Callback; import angularBeans.io.ByteArrayCache; import angularBeans.io.Call; import angularBeans.io.FileUpload; import angularBeans.io.FileUploadHandler; import angularBeans.io.LobWrapper; import angularBeans.js.cache.StaticJsCache; import angularBeans.realtime.RealTime; import angularBeans.util.AngularBeansUtils; import angularBeans.util.ClosureCompiler; import angularBeans.util.CommonUtils; import angularBeans.util.CurrentNGSession; import angularBeans.util.NGBean; import angularBeans.validation.BeanValidationProcessor; /** * <p> * ModuleGenerator is the main component for javascript generation. This class is a session scoped CDI component. This * class uses the registered beans in BeanRegistry during application deployment to generate a minified script. * </p> * * @see BeanRegistry * @author Bessem Hmidi * @author Aymen Naili */ @SuppressWarnings("serial") @SessionScoped public class ModuleGenerator implements Serializable { @Inject ByteArrayCache cache; @Inject BeanLocator locator; @Inject HttpSession httpSession; @Inject BeanValidationProcessor validationAdapter; @Inject AngularBeansUtils util; @Inject transient FileUploadHandler uploadHandler; @Inject transient CurrentNGSession ngSession; ClosureCompiler compiler = new ClosureCompiler(); private String contextPath; private String sessionID; public ModuleGenerator() { } /** * since the NGSession scope lifecycle is the same as the current HTTP session a unique sessionId by http session, * we use the same session id */ @PostConstruct public void init() { sessionID = httpSession.getId();// String.valueOf(UUID.randomUUID()); NGSessionScopeContext.setCurrentContext(sessionID); ngSession.setSessionId(sessionID); } public synchronized String getUID() { return sessionID; } /** * this method generate the angular-beans.js content and write it to the <br> * jsBuffer used by BootServlet * * @param jsBuffer */ public StringBuffer generateScript() { StringBuffer jsBuffer = new StringBuffer(); String sessionPart = String.format("var sessionId=\"%s\";", sessionID); jsBuffer.append(sessionPart); jsBuffer.append(StaticJsCache.CORE_SCRIPT); StringBuffer beansBuffer = new StringBuffer(); for (NGBean mb : BeanRegistry.INSTANCE.getAngularBeans()) { beansBuffer.append(generateBean(mb)); } jsBuffer.append(compiler.getCompressedJavaScript(beansBuffer.toString())); if (StaticJsCache.VALIDATION_SCRIPT.length() == 0) { validationAdapter.build(); } jsBuffer.append(StaticJsCache.VALIDATION_SCRIPT); jsBuffer.append(StaticJsCache.EXTENTIONS_SCRIPT.toString()); return jsBuffer; } /** * this method concern is the generation of the AngularJS service from the @AngularBean CDI bean. * * @param bean * the bean wrapper for an @AngularBean CDI bean. * @return a StringBuffer containing the generated angular service code. */ public StringBuffer generateBean(NGBean bean) { Object reference = locator.lookup(bean.getName(), sessionID); StringBuffer buffer = new StringBuffer(); Class<? extends Object> clazz = bean.getTargetClass(); Method[] methods = bean.getMethods(); buffer.append(";app.factory('" + bean.getName() + "',function " + bean.getName() + "("); // writer.write("['$rootScope','$scope','$http','$location','logger','responseHandler','realtimeManager'',function"); buffer.append("$rootScope, $http, $location,logger,responseHandler,$q"); buffer.append(",realtimeManager"); buffer.append("){\n"); // writer.write("var deffered = $q.defer();"); buffer.append("var " + bean.getName() + "={serviceID:'" + bean.getName() + "'};");// ,scopes:[]};"); buffer.append("\nvar rpath=$rootScope.baseUrl+'" // + contextPath + "http/invoke/service/';\n"); for (Method m : bean.getMethods()) { if (m.isAnnotationPresent(Eval.class)) { Callback callback = m.getAnnotation(Eval.class).value(); try { String execution = (String) m.invoke(reference); String js = ""; if (callback.equals(BEFORE_SESSION_READY)) { js = execution; } if (callback.equals(AFTER_SESSION_READY)) { js = "setTimeout(listen,500);" + "function listen(){" + " if(realtimeManager.ready){" + execution + " }" + " else" + " setTimeout(listen,500);" + "}"; } buffer.append(js); } catch (ClassCastException e) { throw new RuntimeException( "for bean name: " + bean.getName() + " --> an @Eval bloc must return a String"); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { e.printStackTrace(); } } } for (Method get : bean.getters()) { Object result = null; String getter = get.getName(); String modelName = CommonUtils.obtainFieldNameFromAccessor(getter); if (get.getReturnType().equals(LobWrapper.class)) { String uid = String.valueOf(UUID.randomUUID()); cache.getCache().put(uid, new Call(reference, get)); result = contextPath + "lob/" + uid; buffer.append(bean.getName() + "." + modelName + "='" + result + "';"); continue; } validationAdapter.processBeanValidationParsing(get); Method m; try { m = bean.getTargetClass().getMethod((getter)); result = m.invoke(reference); if ((result == null && (m.getReturnType().equals(String.class)))) result = ""; if (result == null) continue; Class<? extends Object> resultClazz = result.getClass(); if (!resultClazz.isPrimitive()) { result = util.getJson(result); } } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } buffer.append(bean.getName() + "." + modelName + "=" + result + ";"); } for (Method m : bean.getMethods()) { if (m.isAnnotationPresent(FileUpload.class)) { String uploadPath = m.getAnnotation(FileUpload.class).path(); Call call = new Call(reference, m); uploadHandler.getUploadsActions().put(uploadPath, call); } } buffer.append(generateStaticPart(bean).toString()); buffer.append(");\n"); return buffer; } /** * * @param bean * the CDI bean wrapper * @return StringBuffer containing the javaScript code of the static (non properties values dependent) code. by * static parts we mean the JS code that can be generated from the java class of the bean (to initialize the * angularJs service we need to call getters on the CDI bean instance and that is considered as the dynamic * part of the angularBean javascript generation) */ private StringBuffer generateStaticPart(NGBean bean) { StringBuffer cachedStaticPart = new StringBuffer(); if (StaticJsCache.CACHED_BEAN_STATIC_PART.containsKey(bean.getTargetClass())) { return StaticJsCache.CACHED_BEAN_STATIC_PART.get(bean.getTargetClass()); } Method[] nativesMethods = Object.class.getMethods(); for (Method m : bean.getMethods()) { boolean corsEnabled = false; boolean isNative = false; for (Method nativeMethod : nativesMethods) { if (nativeMethod.equals(m) && !Modifier.isVolatile(m.getModifiers())) isNative = true; } if (isNative) continue; if ((!CommonUtils.isSetter(m)) && (!CommonUtils.isGetter(m)) ) { String[] csUpdates = null; Set<Method> setters = new HashSet<>(); HttpMethod httpMethod = GET; if (m.isAnnotationPresent(Eval.class)) continue; if (m.isAnnotationPresent(CORS.class)) { corsEnabled = true; } if (m.isAnnotationPresent(Get.class)) { httpMethod = GET; } if (m.isAnnotationPresent(Post.class)) { httpMethod = POST; } if (m.isAnnotationPresent(Delete.class)) { httpMethod = DELETE_TO_REPLACE; } if (m.isAnnotationPresent(Put.class)) { httpMethod = PUT; } if (m.isAnnotationPresent(NGReturn.class)) { NGReturn returns = m.getAnnotation(NGReturn.class); csUpdates = returns.updates(); } if (m.isAnnotationPresent(NGSubmit.class)) { String[] models = m.getAnnotation(NGSubmit.class).backEndModels(); if (models.length == 1 && models[0].equals("*")) { pushScope(bean.getMethods(), setters); } else { for (String model : models) { for (Method md : bean.getMethods()) { if (CommonUtils.isSetter(md)) { String methodName = md.getName(); String modelName = CommonUtils.obtainFieldNameFromAccessor(methodName); if (modelName.equals(model)) { setters.add(md); } } } } } } cachedStaticPart .append("angularBeans.addMethod(" + bean.getName() + ",'" + m.getName() + "',function("); // --------------------------------------------- // Handle args // --------------------------------------------- Type[] parameters = m.getParameterTypes(); if ((!m.isAnnotationPresent(FileUpload.class))) { if (parameters.length > 0) { String argsString = ""; for (int i = 0; i < parameters.length; i++) { argsString += ("arg" + i + ","); } cachedStaticPart.append(argsString.substring(0, argsString.length() - 1)); } } cachedStaticPart.append(") {") .append("var mainReturn={data:{}};").append("var params={};");// sessionUID:$rootScope.sessionUID //-- cachedStaticPart.append(addParams(bean, setters, m, parameters)); if (m.isAnnotationPresent(RealTime.class)) { cachedStaticPart.append("return realtimeManager.call(" + bean.getName() + ",'" + bean.getName() + "." + m.getName() + "',params"); cachedStaticPart.append(").then(function(response) {\n"); cachedStaticPart.append("var msg=(response);"); cachedStaticPart.append( "mainReturn.data= responseHandler.handleResponse(msg," + bean.getName() + ",true);"); cachedStaticPart.append("return mainReturn.data;"); // }"); cachedStaticPart.append("} ,function(response){return $q.reject(response.data);});"); } else { cachedStaticPart.append("\n return $http." + httpMethod.method() + "(rpath+'" + bean.getName() + "/" + m.getName()); if (corsEnabled) { cachedStaticPart.append("/CORS"); corsEnabled = false; } else { cachedStaticPart.append("/JSON"); } if (httpMethod.equals(POST)) { cachedStaticPart.append("',params"); } else { // encodeURI String paramsQuery = ("?params='+encodeURIComponent(angular.toJson(params))"); cachedStaticPart.append(paramsQuery); } cachedStaticPart.append(").then(function(response) {\n"); cachedStaticPart.append("var msg=response.data;"); cachedStaticPart.append( "mainReturn.data= responseHandler.handleResponse(msg," + bean.getName() + ",true);"); cachedStaticPart.append("return mainReturn.data;"); cachedStaticPart.append("} ,function(response){return $q.reject(response.data);});"); } cachedStaticPart.append("});"); if ((!CommonUtils.isSetter(m)) && (!CommonUtils.isGetter(m))) { if (m.isAnnotationPresent(NGPostConstruct.class)) { cachedStaticPart.append("realtimeManager.onReadyState(function(){"); cachedStaticPart.append(bean.getName() + "." + m.getName() + "();\n"); cachedStaticPart.append("});"); } } } } cachedStaticPart.append("return " + bean.getName() + ";} \n"); StaticJsCache.CACHED_BEAN_STATIC_PART.put(bean.getClass(), cachedStaticPart); return cachedStaticPart; } private void pushScope(Method[] methods, Set<Method> setters) { for (Method md : methods) { if (CommonUtils.isSetter(md)) { setters.add(md); } } } private StringBuffer addParams(NGBean bean, Set<Method> setters, Method m, Type[] args) { StringBuffer sb = new StringBuffer(); for (Method setter : setters) { String name = CommonUtils.obtainFieldNameFromAccessor(setter.getName()); sb.append("params['" + name + "']=" + bean.getName() + "." + name + ";"); } if (args.length > 0) { String argsString = ""; for (int i = 0; i < args.length; i++) { argsString += "arg" + i + ","; } argsString = argsString.substring(0, argsString.length() - 1); sb.append("params['args']=[" + argsString + "];\n"); } return sb; } public void setContextPath(String contextPath) { util.setContextPath(contextPath); this.contextPath = contextPath; } public String getContextPath() { return contextPath; } }