001    /**
002     * Copyright 2004-2013 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.student.remote.impl.mojo;
017    
018    import java.util.ArrayList;
019    import java.util.HashSet;
020    import java.util.List;
021    import java.util.Set;
022    import java.util.Stack;
023    import org.kuali.rice.core.api.criteria.Predicate;
024    import org.kuali.rice.core.api.criteria.PredicateFactory;
025    import org.kuali.rice.core.api.criteria.QueryByCriteria;
026    import org.kuali.student.admin.ui.mojo.AdminUiLookupableWriter;
027    import org.kuali.student.contract.model.MessageStructure;
028    import org.kuali.student.contract.model.Service;
029    
030    import org.kuali.student.contract.model.ServiceContractModel;
031    import org.kuali.student.contract.model.ServiceMethod;
032    import org.kuali.student.contract.model.ServiceMethodError;
033    import org.kuali.student.contract.model.ServiceMethodParameter;
034    import org.kuali.student.contract.model.XmlType;
035    import org.kuali.student.contract.model.util.ModelFinder;
036    import org.kuali.student.contract.writer.JavaClassWriter;
037    import org.kuali.student.contract.writer.service.GetterSetterNameCalculator;
038    import org.kuali.student.contract.writer.service.MessageStructureTypeCalculator;
039    import org.kuali.student.contract.writer.service.ServiceExceptionWriter;
040    import org.slf4j.Logger;
041    import org.slf4j.LoggerFactory;
042    
043    /**
044     *
045     * @author nwright
046     */
047    public class RemoteImplServiceTestWriter extends JavaClassWriter {
048    
049        private static final Logger log = LoggerFactory.getLogger(RemoteImplServiceTestWriter.class);
050        private ServiceContractModel model;
051        private ModelFinder finder;
052        private String directory;
053        private String rootPackage;
054        private String servKey;
055        private List<ServiceMethod> methods;
056        private Service service;
057    
058        public RemoteImplServiceTestWriter(ServiceContractModel model,
059                String directory,
060                String rootPackage,
061                String servKey,
062                List<ServiceMethod> methods) {
063            super(directory + "/test/java", calcPackage(servKey, rootPackage), calcClassName(servKey));
064            this.model = model;
065            this.finder = new ModelFinder(model);
066            this.directory = directory;
067            this.rootPackage = rootPackage;
068            this.servKey = servKey;
069            this.service = finder.findService(servKey);
070            this.methods = methods;
071        }
072    
073        public static String calcPackage(String servKey, String rootPackage) {
074    //        String pack = rootPackage + "." + servKey.toLowerCase() + ".";
075    //  StringBuffer buf = new StringBuffer (service.getVersion ().length ());
076    //  for (int i = 0; i < service.getVersion ().length (); i ++)
077    //  {
078    //   char c = service.getVersion ().charAt (i);
079    //   c = Character.toLowerCase (c);
080    //   if (Character.isLetter (c))
081    //   {
082    //    buf.append (c);
083    //    continue;
084    //   }
085    //   if (Character.isDigit (c))
086    //   {
087    //    buf.append (c);
088    //   }
089    //  }
090    //  pack = pack + buf.toString ();
091    //        pack = pack + "service.decorators";
092    //        return pack;
093            return rootPackage;
094        }
095    
096        public static String calcClassName(String servKey) {
097            String name = GetterSetterNameCalculator.calcInitUpper(servKey + "ServiceRemoteImplTest");
098            if (name.startsWith("RICE.")) {
099                name = name.substring("RICE.".length());
100            }
101            return name;
102        }
103    
104        private static enum MethodType {
105    
106            VALIDATE, CREATE, UPDATE
107        };
108    
109        private MethodType calcMethodType(ServiceMethod method) {
110            if (method.getName().startsWith("validate")) {
111                return MethodType.VALIDATE;
112            }
113            if (method.getName().startsWith("create")) {
114                return MethodType.CREATE;
115            }
116            if (method.getName().startsWith("update")) {
117                return MethodType.UPDATE;
118            }
119            return null;
120        }
121    
122        /**
123         * Write out the entire file
124         *
125         * @param out
126         */
127        public void write() {
128            String serviceName = service.getName();
129            indentPrintln("//@Ignore");
130            indentPrintln("public class " + calcClassName(servKey));
131            // TODO: figure out how to add import for the decorator
132            openBrace();
133            importsAdd("org.junit.*");
134            XmlType contextInfo = finder.findXmlType("contextInfo");
135            importsAdd(contextInfo.getJavaPackage() + "." + contextInfo.getName());
136            indentPrintln("private static ContextInfo contextInfo;");
137    
138            indentPrintln("private static " + serviceName + "RemoteImpl service;");
139            indentPrintln("");
140            indentPrintln("");
141            indentPrintln("@BeforeClass");
142            indentPrintln("public static void setUpClass() throws Exception");
143            openBrace();
144            indentPrintln("service = new " + serviceName + "RemoteImpl();");
145            indentPrintln("service.setHostUrl(RemoteServiceConstants.ENV2_URL);");
146            indentPrintln("//service.setHostUrl(RemoteServiceConstants.LOCAL_HOST_EMBEDDED_URL);");
147            indentPrintln("contextInfo = new ContextInfo();");
148            indentPrintln("contextInfo.setPrincipalId(\"TESTUSER\");");
149            closeBrace();
150            indentPrintln("");
151            indentPrintln("@AfterClass");
152            indentPrintln("public static void tearDownClass() throws Exception");
153            openBrace();
154            closeBrace();
155            indentPrintln("");
156            indentPrintln("@Before");
157            indentPrintln("public void setUp()");
158            openBrace();
159            closeBrace();
160            indentPrintln("");
161            indentPrintln("@After");
162            indentPrintln("public void tearDown()");
163            openBrace();
164            closeBrace();
165            indentPrintln("");
166    
167    
168            Set<XmlType> types = this.getMainXmlTypesUsedByService(methods);
169            if (types.isEmpty()) {
170                log.warn("No types defined for servKey: " + servKey);
171                return;
172            }
173            // the main servKey
174            log.info("Generating search by criteria tests for " + types.size() + " in " + servKey);
175            for (XmlType type : types) {
176                this.writeTestMethodsForXmlType(type);
177            }
178            closeBrace();
179    
180            this.writeJavaClassAndImportsOutToFile();
181            this.getOut().close();
182        }
183    
184        private void writeTestMethodsForXmlType(XmlType xmlType) {
185            ServiceMethod searchMethod = AdminUiLookupableWriter.findSearchMethod(xmlType, methods);
186            if (searchMethod == null) {
187                log.warn("No search method found for " + this.servKey + "." + xmlType.getName());
188                return;
189    //            throw new NullPointerException (this.servKey + "." + xmlType.getName());
190            }
191            String infoClass = GetterSetterNameCalculator.calcInitUpper(xmlType.getName());
192            importsAdd(xmlType.getJavaPackage() + "." + xmlType.getName());
193            indentPrintln("");
194            indentPrintln("@Test");
195            indentPrintln("public void testSearch" + infoClass + "All () throws Exception");
196            openBrace();
197            importsAdd(QueryByCriteria.class.getName());
198            importsAdd(Predicate.class.getName());
199            importsAdd(PredicateFactory.class.getName());
200            indentPrintln("QueryByCriteria.Builder qBuilder = QueryByCriteria.Builder.create();");
201            indentPrintln("qBuilder.setMaxResults (30);");
202            importsAdd(List.class.getName());
203            String returnType = "List<" + infoClass + ">";
204            if (searchMethod.getReturnValue().getType().endsWith("QueryResults")) {
205                returnType = searchMethod.getReturnValue().getType();
206                XmlType returnXmlType = finder.findXmlType(returnType);
207                importsAdd (returnXmlType.getJavaPackage() + "." + returnType);
208            }
209            indentPrint(returnType + " infos = service." + searchMethod.getName());
210            String comma = "(";
211            for (ServiceMethodParameter param : searchMethod.getParameters()) {
212                print(comma);
213                comma = ", ";
214                if (param.getType().equals("QueryByCriteria")) {
215                    print("qBuilder.build()");
216                    continue;
217                }
218                if (param.getType().equals("ContextInfo")) {
219                    print("contextInfo");
220                    continue;
221                }
222                log.warn(servKey + "." + searchMethod.getName() + " param=" + param.getName() + " " + param.getType() + " cannot be specified in search");
223            }
224            println(");");
225            closeBrace();
226    
227    
228            indentPrintln("");
229            indentPrintln("@Test");
230            indentPrintln("public void testSearch" + infoClass + "KeywordSearch () throws Exception");
231            openBrace();
232            importsAdd(QueryByCriteria.class.getName());
233            importsAdd(Predicate.class.getName());
234            importsAdd(PredicateFactory.class.getName());
235            indentPrintln("QueryByCriteria.Builder qBuilder = QueryByCriteria.Builder.create();");
236            importsAdd(ArrayList.class.getName());
237            indentPrintln("List<Predicate> pList = new ArrayList<Predicate>();");
238            indentPrintln("pList.add(PredicateFactory.equal(\"keywordSearch\", \"xyzzysomethingnothingmatches\"));");
239            indentPrintln("qBuilder.setPredicates(PredicateFactory.and(pList.toArray(new Predicate[pList.size()])));");
240            indentPrintln("qBuilder.setMaxResults (30);");
241            importsAdd(List.class.getName());
242            indentPrint(returnType + " infos = service." + searchMethod.getName());
243            comma = "(";
244            for (ServiceMethodParameter param : searchMethod.getParameters()) {
245                print(comma);
246                comma = ", ";
247                if (param.getType().equals("QueryByCriteria")) {
248                    print("qBuilder.build()");
249                    continue;
250                }
251                if (param.getType().equals("ContextInfo")) {
252                    print("contextInfo");
253                    continue;
254                }
255                log.warn(servKey + "." + searchMethod.getName() + " param=" + param.getName() + " " + param.getType() + " cannot be specified in search");
256            }
257            println(");");
258            closeBrace();
259    
260            this.writeFieldTests(infoClass, returnType, searchMethod, xmlType, new Stack<XmlType>(), "");
261        }
262    
263        private void writeFieldTests(String infoClass, String returnType, ServiceMethod searchMethod, XmlType type, Stack<XmlType> parents, String prefix) {
264            // avoid recursion
265            if (parents.contains(type)) {
266                return;
267            }
268            parents.push(type);
269            for (MessageStructure ms : finder.findMessageStructures(type.getName())) {
270                String fieldName = GetterSetterNameCalculator.calcInitLower(ms.getShortName());
271                if (!prefix.isEmpty()) {
272                    fieldName = prefix + "." + fieldName;
273                }
274                String fieldNameCamel = GetterSetterNameCalculator.dot2Camel(fieldName);
275                if (ms.getShortName().equalsIgnoreCase("versionInd")) {
276                    indentPrintln("// TODO: deal with seaching on the version indicator which is a string in the contract but a number in the database");
277                    continue;
278                }
279                if (ms.getType().equalsIgnoreCase("AttributeInfoList")) {
280                    indentPrintln("// TODO: deal with dynamic attributes");
281                    continue;
282                }
283                if (ms.getType().endsWith("List")) {
284                    indentPrintln("// TODO: deal with  " + fieldName + " which is a List");
285                    continue;
286                }
287                XmlType fieldType = finder.findXmlType(ms.getType());
288                if (fieldType.getPrimitive().equalsIgnoreCase(XmlType.COMPLEX)) {
289                    // complex sub-types such as rich text 
290                    this.writeFieldTests(infoClass, returnType, searchMethod, fieldType, parents, fieldName);
291                    continue;
292                }
293                if (!ms.getType().equalsIgnoreCase("String")) {
294                    indentPrintln("// TODO: deal with  " + fieldName + " which is a " + ms.getType());
295                    continue;
296                }
297    
298                indentPrintln("");
299                indentPrintln("@Test");
300                indentPrintln("public void testSearch" + infoClass + "By" + fieldNameCamel + " () throws Exception");
301                openBrace();
302                indentPrintln("QueryByCriteria.Builder qBuilder = QueryByCriteria.Builder.create();");
303                indentPrintln("List<Predicate> pList = new ArrayList<Predicate>();");
304                indentPrintln("pList.add(PredicateFactory.equal(\"" + fieldName + "\", \"xyzzysomethingnothingmatches\"));");
305                indentPrintln("qBuilder.setPredicates(PredicateFactory.and(pList.toArray(new Predicate[pList.size()])));");
306                indentPrintln("qBuilder.setMaxResults (30);");
307                indentPrint(returnType + " infos = service." + searchMethod.getName());
308                String comma = "(";
309                for (ServiceMethodParameter param : searchMethod.getParameters()) {
310                    print(comma);
311                    comma = ", ";
312                    if (param.getType().equals("QueryByCriteria")) {
313                        print("qBuilder.build()");
314                        continue;
315                    }
316                    if (param.getType().equals("ContextInfo")) {
317                        print("contextInfo");
318                        continue;
319                    }
320                    log.warn(servKey + "." + searchMethod.getName() + " param=" + param.getName() + " " + param.getType() + " cannot be specified in search");
321                }
322                println(");");
323                closeBrace();
324    
325            }
326            parents.pop();
327        }
328    
329        private String calcType(String type, String realType) {
330            XmlType t = finder.findXmlType(this.stripList(type));
331            return MessageStructureTypeCalculator.calculate(this, model, type, realType,
332                    t.getJavaPackage());
333        }
334    
335        private String stripList(String str) {
336            return GetterSetterNameCalculator.stripList(str);
337        }
338    
339        private String calcExceptionClassName(ServiceMethodError error) {
340            if (error.getClassName() == null) {
341                return ServiceExceptionWriter.calcClassName(error.getType());
342            }
343            return error.getClassName();
344        }
345    
346        private String calcExceptionPackageName(ServiceMethodError error) {
347            if (error.getClassName() == null) {
348                return ServiceExceptionWriter.calcPackage(rootPackage);
349            }
350            return error.getPackageName();
351        }
352    
353        private Set<XmlType> getMainXmlTypesUsedByService(List<ServiceMethod> methods) {
354            Set<XmlType> set = new HashSet();
355            for (ServiceMethod method : methods) {
356                if (method.getReturnValue().getType().endsWith("List")) {
357                    continue;
358                }
359                XmlType returnType = finder.findXmlType(method.getReturnValue().getType());
360                if (returnType == null) {
361                    continue;
362                }
363                if (!returnType.getPrimitive().equalsIgnoreCase(XmlType.COMPLEX)) {
364                    continue;
365                }
366                // TYPE only should show up on type service
367                if (returnType.getName().equalsIgnoreCase("TypeInfo")) {
368                    if (!servKey.equalsIgnoreCase("type")) {
369                        continue;
370                    }
371                }
372                // State only should show up on type service
373                if (returnType.getName().equalsIgnoreCase("StateInfo")) {
374                    if (!servKey.equalsIgnoreCase("state")) {
375                        continue;
376                    }
377                }
378    //            if (method.getName().startsWith("create")) {
379    //                set.add(returnType);
380    //                continue;
381    //            }
382    //            if (method.getName().startsWith("update")) {
383    //                set.add(returnType);
384    //                continue;
385    //            }
386                // presume a get with a single parameter is getting by primary key
387                if (method.getName().startsWith("get")) {
388                    List<ServiceMethodParameter> params = this.getNonContextParameters(method.getParameters());
389                    if (params.size() == 1) {
390                        if (params.get(0).getType().equalsIgnoreCase("String")) {
391                            set.add(returnType);
392                            continue;
393                        }
394                    }
395                }
396            }
397            return set;
398        }
399    
400        
401        private List<ServiceMethodParameter>  getNonContextParameters (List <ServiceMethodParameter> params) {
402             List<ServiceMethodParameter> list = new ArrayList<ServiceMethodParameter> ();
403             for (ServiceMethodParameter param : params) {
404                 if (param.getType().equalsIgnoreCase("ContextInfo")) {
405                     continue;
406                 }
407                 list.add (param);
408             }
409             return list;
410        }
411        private List<MessageStructure> getFieldsToSearchOn(XmlType xmlType) {
412            List<MessageStructure> list = new ArrayList<MessageStructure>();
413            for (MessageStructure ms : finder.findMessageStructures(xmlType.getName())) {
414                XmlType msType = finder.findXmlType(ms.getType());
415                // TODO: dive down into complex sub-types 
416                if (msType.getPrimitive().equalsIgnoreCase(XmlType.COMPLEX)) {
417                    continue;
418                }
419                list.add(ms);
420            }
421            return list;
422        }
423    }