001/**
002 * Copyright 2004-2014 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 */
016package org.kuali.student.remote.impl.mojo;
017
018import java.util.ArrayList;
019import java.util.HashSet;
020import java.util.List;
021import java.util.Set;
022import java.util.Stack;
023import org.kuali.rice.core.api.criteria.Predicate;
024import org.kuali.rice.core.api.criteria.PredicateFactory;
025import org.kuali.rice.core.api.criteria.QueryByCriteria;
026import org.kuali.student.admin.ui.mojo.AdminUiLookupableWriter;
027import org.kuali.student.contract.model.MessageStructure;
028import org.kuali.student.contract.model.Service;
029
030import org.kuali.student.contract.model.ServiceContractModel;
031import org.kuali.student.contract.model.ServiceMethod;
032import org.kuali.student.contract.model.ServiceMethodError;
033import org.kuali.student.contract.model.ServiceMethodParameter;
034import org.kuali.student.contract.model.XmlType;
035import org.kuali.student.contract.model.util.ModelFinder;
036import org.kuali.student.contract.writer.JavaClassWriter;
037import org.kuali.student.contract.writer.service.GetterSetterNameCalculator;
038import org.kuali.student.contract.writer.service.MessageStructureTypeCalculator;
039import org.kuali.student.contract.writer.service.ServiceExceptionWriter;
040import org.slf4j.Logger;
041import org.slf4j.LoggerFactory;
042
043/**
044 *
045 * @author nwright
046 */
047public 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}