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.jpa.mojo;
017
018import org.kuali.student.contract.model.Service;
019import org.kuali.student.contract.model.ServiceContractModel;
020import org.kuali.student.contract.model.ServiceMethod;
021import org.kuali.student.contract.model.ServiceMethodError;
022import org.kuali.student.contract.model.ServiceMethodParameter;
023import org.kuali.student.contract.model.XmlType;
024import org.kuali.student.contract.model.util.ModelFinder;
025import org.kuali.student.contract.writer.JavaClassWriter;
026import org.kuali.student.contract.writer.service.GetterSetterNameCalculator;
027import org.kuali.student.contract.writer.service.MessageStructureTypeCalculator;
028import org.kuali.student.contract.writer.service.ServiceExceptionWriter;
029import org.slf4j.Logger;
030import org.slf4j.LoggerFactory;
031
032import java.util.*;
033import org.kuali.rice.core.api.criteria.CriteriaLookupService;
034import org.kuali.rice.core.api.criteria.GenericQueryResults;
035import org.springframework.transaction.annotation.Transactional;
036
037/**
038 *
039 * @author nwright
040 */
041public class JpaImplServiceWriter extends JavaClassWriter {
042
043    private static final Logger log = LoggerFactory.getLogger(JpaImplServiceWriter.class);
044
045    //////////////////////////////
046    // Constants
047    //////////////////////////////
048    /**
049     * The standard type of methods used in our Service contract.
050     */
051    protected static enum MethodType {
052
053        VALIDATE,
054        CREATE,
055        CREATE_BULK,
056        ADD,
057        UPDATE,
058        UPDATE_OTHER,
059        DELETE,
060        REMOVE,
061        DELETE_OTHER,
062        GET_CREATE,
063        GET_BY_ID,
064        GET_BY_IDS,
065        RICE_GET_BY_NAMESPACE_AND_NAME,
066        GET_IDS_BY_TYPE,
067        GET_IDS_BY_OTHER,
068        GET_INFOS_BY_OTHER,
069        GET_TYPE,
070        GET_TYPES,
071        SEARCH_FOR_IDS,
072        SEARCH_FOR_INFOS,
073        UNKNOWN
074    };
075
076    //////////////////////////////
077    // Data Variables
078    //////////////////////////////
079    protected ServiceContractModel model;
080    protected ModelFinder finder;
081    private String directory;
082    /**
083     * The package name is stored in the service object itself (the package spec
084     * kept moving around so I assumed the actual service name was unique but
085     * ran into a problem when we included rice because they have a StateService
086     * meaning US states and we have a StateService meaning the state of the
087     * object so I added logic to detect rice and prepend that "RICE." to it
088     */
089    private String rootPackage;
090
091    /**
092     * Name of the service being operated on. If it is a RICE service it is
093     * prefixed with RICE. [11:32:18 AM] Norman Wright: short name... I think it
094     * gets it by taking the java class SimpleName and stripping off the word
095     * "Service" and I think making it lower case. [11:32:24 AM] Norman Wright:
096     * so OrganizationService becomes organization
097     */
098    protected String servKey;
099
100    protected List<ServiceMethod> methods;
101
102    /**
103     * A flag that holds if the service is an R1 service.
104     */
105    private boolean isR1;
106
107    //////////////////////////
108    // Constructor
109    //////////////////////////
110    public JpaImplServiceWriter(ServiceContractModel model,
111            String directory,
112            String rootPackage,
113            String servKey,
114            List<ServiceMethod> methods,
115            boolean isR1) {
116        super(directory, calcPackage(servKey, rootPackage), calcClassName(servKey));
117        this.model = model;
118        this.finder = new ModelFinder(model);
119        this.directory = directory;
120        this.rootPackage = rootPackage;
121        this.servKey = servKey;
122        this.methods = methods;
123        this.isR1 = isR1;
124    }
125
126    public JpaImplServiceWriter(ServiceContractModel model,
127            String directory,
128            String rootPackage,
129            String servKey,
130            List<ServiceMethod> methods,
131            boolean isR1,
132            String packageName,
133            String className) {
134        super(directory, packageName, className);
135        this.model = model;
136        this.finder = new ModelFinder(model);
137        this.directory = directory;
138        this.rootPackage = rootPackage;
139        this.servKey = servKey;
140        this.methods = methods;
141        this.isR1 = isR1;
142    }
143
144    /////////////////////////
145    // Functional Methods
146    /////////////////////////
147    /**
148     * Returns the jpa implementation package name.
149     *
150     * @param servKey
151     * @param rootPackage
152     * @return
153     */
154    public static String calcPackage(String servKey, String rootPackage) {
155        String pack = rootPackage + ".";
156//        String pack = rootPackage + "." + servKey.toLowerCase() + ".";
157//  StringBuffer buf = new StringBuffer (service.getVersion ().length ());
158//  for (int i = 0; i < service.getVersion ().length (); i ++)
159//  {
160//   char c = service.getVersion ().charAt (i);
161//   c = Character.toLowerCase (c);
162//   if (Character.isLetter (c))
163//   {
164//    buf.append (c);
165//    continue;
166//   }
167//   if (Character.isDigit (c))
168//   {
169//    buf.append (c);
170//   }
171//  }
172//  pack = pack + buf.toString ();
173        pack = pack + "service.impl.jpa." + servKey.toLowerCase();
174        return pack;
175    }
176
177    /**
178     * Checks if this is a RICE service.
179     *
180     * @return true if this is a RICE service.
181     */
182    private boolean isRice() {
183        if (this.servKey.startsWith("RICE.")) {
184            return true;
185        }
186        return false;
187    }
188
189    protected static String fixServKey(String servKey) {
190        if (servKey.startsWith("RICE.")) {
191            return servKey.substring("RICE.".length());
192        }
193        return servKey;
194    }
195
196    /**
197     * Given the service key (name), returns a calculated class name for the jpa
198     * impl.
199     */
200    public static String calcClassName(String servKey) {
201        return GetterSetterNameCalculator.calcInitUpper(fixServKey(servKey) + "ServiceJpaImpl");
202    }
203
204    public static String calcServiceInterfaceClassName(String servKey) {
205        return GetterSetterNameCalculator.calcInitUpper(fixServKey(servKey) + "Service");
206    }
207
208    /**
209     * Analyses the method and returns a MethodType enum that describes what
210     * type of method this is.
211     */
212    protected MethodType calcMethodType(ServiceMethod method) {
213//        if (method.getName().equals("getInstructionalDaysForTerm")) {
214//            System.out.println("debug here");
215//        }
216        if (this.isRice()) {
217            if (method.getName().contains("ByNamespaceCodeAndName")) {
218                return MethodType.RICE_GET_BY_NAMESPACE_AND_NAME;
219            }
220            if (method.getName().contains("ByNameAndNamespace")) {
221                return MethodType.RICE_GET_BY_NAMESPACE_AND_NAME;
222            }
223            if (method.getName().startsWith("get")) {
224                if (method.getParameters().size() == 1) {
225                    if (!method.getReturnValue().getType().endsWith("List")) {
226                        if (method.getParameters().get(0).getName().equals("id")) {
227                            return MethodType.GET_BY_ID;
228                        }
229
230                    } else {
231                        if (method.getParameters().get(0).getName().equals("ids")) {
232                            return MethodType.GET_BY_IDS;
233                        }
234                    }
235                }
236            }
237        }
238        if (method.getName().startsWith("validate")) {
239            return MethodType.VALIDATE;
240        }
241        if (method.getName().startsWith("create")) {
242            if (method.getName().startsWith("createBatch")) {
243                return MethodType.CREATE_BULK;
244            }
245            if (method.getName().startsWith("createSocRolloverResultItems")) {
246                return MethodType.CREATE_BULK;
247            }
248            if (method.getName().contains("FromExisting")) {
249                return MethodType.CREATE_BULK;
250            }
251            ServiceMethodParameter infoParam = this.findInfoParameter(method);
252            if (infoParam == null) {
253                return MethodType.CREATE_BULK;
254            }
255            if (method.getReturnValue().getType().endsWith("List")) {
256                return MethodType.CREATE_BULK;
257            }
258            return MethodType.CREATE;
259        }
260        if (method.getName().startsWith("add")) {
261            return MethodType.ADD;
262        }
263        if (method.getName().startsWith("update")) {
264            if (this.findInfoParameter(method) != null) {
265                return MethodType.UPDATE;
266            }
267            return MethodType.UPDATE_OTHER;
268        }
269        if (method.getName().startsWith("delete")) {
270            if (method.getName().contains("By")) {
271                if (!method.getName().startsWith("deleteBy")) {
272                    return MethodType.DELETE_OTHER;
273                }
274            }
275            if (method.getName().contains("For")) {
276                if (!method.getName().startsWith("deleteFor")) {
277                    return MethodType.DELETE_OTHER;
278                }
279            }
280            return MethodType.DELETE;
281        }
282        if (method.getName().startsWith("remove")) {
283            return MethodType.REMOVE;
284        }
285
286        if (method.getName().startsWith("getCreate")) {
287            return MethodType.GET_CREATE;
288        }
289
290        if (method.getName().startsWith("get")) {
291            if (method.getName().endsWith("ByIds")) {
292                return MethodType.GET_BY_IDS;
293            }
294            if (method.getName().endsWith("ByKeys")) {
295                return MethodType.GET_BY_IDS;
296            }
297            if (method.getName().endsWith("ByType")) {
298                return MethodType.GET_IDS_BY_TYPE;
299            }
300            if (method.getReturnValue().getType().endsWith("TypeInfo")) {
301                return MethodType.GET_TYPE;
302            }
303            if (method.getReturnValue().getType().endsWith("TypeInfoList")) {
304                return MethodType.GET_TYPES;
305            }
306            if (method.getName().endsWith("ByType")) {
307                return MethodType.GET_IDS_BY_TYPE;
308            }
309            String splitName = splitCamelCase(method.getName());
310            if (splitName.contains(" By ")) {
311                if (method.getReturnValue().getType().equals("StringList")) {
312                    return MethodType.GET_IDS_BY_OTHER;
313                }
314                if (method.getReturnValue().getType().endsWith("InfoList")) {
315                    return MethodType.GET_INFOS_BY_OTHER;
316                }
317                return MethodType.UNKNOWN;
318            }
319            if (splitName.contains(" For ")) {
320                if (method.getReturnValue().getType().equals("StringList")) {
321                    return MethodType.GET_IDS_BY_OTHER;
322                }
323                if (method.getReturnValue().getType().endsWith("InfoList")) {
324                    return MethodType.GET_INFOS_BY_OTHER;
325                }
326                return MethodType.UNKNOWN;
327            }
328            if (method.getParameters().size() >= 1 && method.getParameters().size() <= 2) {
329                if (!method.getReturnValue().getType().endsWith("List")) {
330                    if (method.getParameters().get(0).getName().endsWith("Id")) {
331                        return MethodType.GET_BY_ID;
332                    }
333                    if (method.getParameters().get(0).getName().endsWith("Key")) {
334                        return MethodType.GET_BY_ID;
335                    }
336                }
337            }
338        }
339        if (method.getName().startsWith("searchFor")) {
340            if (method.getName().endsWith("Ids")) {
341                return MethodType.SEARCH_FOR_IDS;
342            }
343            if (method.getName().endsWith("Keys")) {
344                return MethodType.SEARCH_FOR_IDS;
345            }
346            return MethodType.SEARCH_FOR_INFOS;
347        }
348
349        return MethodType.UNKNOWN;
350    }
351
352    // got this from
353    // http://stackoverflow.com/questions/2559759/how-do-i-convert-camelcase-into-human-readable-names-in-java
354    private static String splitCamelCase(String s) {
355        if (s == null) {
356            return null;
357        }
358        return s.replaceAll(String.format("%s|%s|%s",
359                "(?<=[A-Z])(?=[A-Z][a-z])", "(?<=[^A-Z])(?=[A-Z])",
360                "(?<=[A-Za-z])(?=[^A-Za-z])"), " ");
361    }
362
363    /**
364     * Write out the entire file
365     */
366    public void write() {
367        indentPrint("public class " + calcClassName(servKey));
368        println(" implements " + calcServiceInterfaceClassName(servKey));
369        Service serv = finder.findService(servKey);
370        importsAdd(serv.getImplProject() + "." + serv.getName());
371        openBrace();
372        // put all the cache variables at the top
373        indentPrintln("// dao variables ");
374        jpaEntitiesWritten.clear();
375        for (ServiceMethod method : methods) {
376            MethodType methodType = calcMethodType(method);
377            switch (methodType) {
378                case CREATE:
379                case GET_TYPE:
380                case GET_BY_ID:
381                    writeDaoVariable(method);
382            }
383        }
384
385        for (ServiceMethod method : methods) {
386            MethodType methodType = calcMethodType(method);
387            println("");
388//            indentPrintln("/**");
389//            indentPrintWrappedComment(method.getDescription());
390//            indentPrintln("* ");
391//            for (ServiceMethodParameter param : method.getParameters()) {
392//                indentPrintWrappedComment("@param " + param.getName() + " - "
393//                        + param.getType() + " - "
394//                        + param.getDescription());
395//            }
396//            indentPrintWrappedComment("@return " + method.getReturnValue().
397//                    getDescription());
398//            indentPrintln("*/");
399            indentPrintln("@Override");
400            switch (methodType) {
401                case CREATE:
402                    indentPrintln("@Transactional(readOnly = false, rollbackFor = {Throwable.class})");
403                    importsAdd(Transactional.class.getName());
404                    break;
405                case ADD:
406                    indentPrintln("@Transactional(readOnly = false, rollbackFor = {Throwable.class})");
407                    importsAdd(Transactional.class.getName());
408                    break;
409                case UPDATE:
410                    indentPrintln("@Transactional(readOnly = false, rollbackFor = {Throwable.class})");
411                    importsAdd(Transactional.class.getName());
412                    break;
413                case DELETE:
414                    indentPrintln("@Transactional(readOnly = false, rollbackFor = {Throwable.class})");
415                    importsAdd(Transactional.class.getName());
416                    break;
417                case REMOVE:
418                    indentPrintln("@Transactional(readOnly = false, rollbackFor = {Throwable.class})");
419                    importsAdd(Transactional.class.getName());
420                    break;
421                case GET_BY_ID:
422                    indentPrintln("@Transactional(readOnly = true, noRollbackFor = {DoesNotExistException.class}, rollbackFor = {Throwable.class}))");
423                    importsAdd(Transactional.class.getName());
424                    break;
425                case GET_BY_IDS:
426                    indentPrintln("@Transactional(readOnly = true, noRollbackFor = {DoesNotExistException.class}, rollbackFor = {Throwable.class}))");
427                    importsAdd(Transactional.class.getName());
428                    break;
429                case GET_IDS_BY_TYPE:
430                    indentPrintln("@Transactional(readOnly = true, noRollbackFor = {DoesNotExistException.class}, rollbackFor = {Throwable.class}))");
431                    importsAdd(Transactional.class.getName());
432                    break;
433                case GET_IDS_BY_OTHER:
434                    indentPrintln("@Transactional(readOnly = true, noRollbackFor = {DoesNotExistException.class}, rollbackFor = {Throwable.class}))");
435                    importsAdd(Transactional.class.getName());
436                    break;
437                case GET_INFOS_BY_OTHER:
438                    indentPrintln("@Transactional(readOnly = true, noRollbackFor = {DoesNotExistException.class}, rollbackFor = {Throwable.class}))");
439                    importsAdd(Transactional.class.getName());
440                    break;
441                case GET_TYPE:
442                    indentPrintln("@Transactional(readOnly = true, noRollbackFor = {DoesNotExistException.class}, rollbackFor = {Throwable.class}))");
443                    importsAdd(Transactional.class.getName());
444                    break;
445                case GET_TYPES:
446                    indentPrintln("@Transactional(readOnly = true, noRollbackFor = {DoesNotExistException.class}, rollbackFor = {Throwable.class}))");
447                    importsAdd(Transactional.class.getName());
448                    break;
449                case RICE_GET_BY_NAMESPACE_AND_NAME:
450                    indentPrintln("@Transactional(readOnly = true, noRollbackFor = {DoesNotExistException.class}, rollbackFor = {Throwable.class}))");
451                    importsAdd(Transactional.class.getName());
452                    break;
453                default:
454                    indentPrintln("//TODO: JPAIMPL check and set the right transactional values for this method");
455                    indentPrintln("//@Transactional(readOnly = true, noRollbackFor = {DoesNotExistException.class}, rollbackFor = {Throwable.class}))");
456                    importsAdd(Transactional.class.getName());
457            }
458            String type = method.getReturnValue().getType();
459            String realType = stripList(type);
460            indentPrint("public " + calcType(type, realType) + " " + method.getName()
461                    + "(");
462            // now do parameters
463            String comma = "";
464            for (ServiceMethodParameter param : method.getParameters()) {
465                type = param.getType();
466                realType = stripList(type);
467                print(comma);
468                print(calcType(type, realType));
469                print(" ");
470                print(param.getName());
471                comma = ", ";
472            }
473            println(")");
474            // now do exceptions
475            comma = "throws ";
476            incrementIndent();
477            for (ServiceMethodError error : method.getErrors()) {
478                indentPrint(comma);
479                String exceptionClassName = calcExceptionClassName(error);
480                String exceptionPackageName = this.calcExceptionPackageName(error);
481                println(exceptionClassName);
482                this.importsAdd(exceptionPackageName + "." + exceptionClassName);
483                comma = "      ,";
484            }
485            decrementIndent();
486            openBrace();
487            indentPrintln("// " + methodType);
488            switch (methodType) {
489                case VALIDATE:
490                    writeValidate(method);
491                    break;
492                case CREATE:
493                    writeCreate(method);
494                    break;
495                case ADD:
496                    writeAdd(method);
497                    break;
498                case UPDATE:
499                    writeUpdate(method);
500                    break;
501                case DELETE:
502                    writeDelete(method);
503                    break;
504                case REMOVE:
505                    writeRemove(method);
506                    break;
507                case GET_BY_ID:
508                    writeGetById(method);
509                    break;
510                case GET_BY_IDS:
511                    writeGetByIds(method);
512                    break;
513                case GET_IDS_BY_TYPE:
514                    writeGetIdsByType(method);
515                    break;
516                case GET_IDS_BY_OTHER:
517                    writeGetIdsByOther(method);
518                    break;
519                case GET_INFOS_BY_OTHER:
520                    writeGetInfosByOther(method);
521                    break;
522                case SEARCH_FOR_IDS:
523                    writeSearchForIds(method);
524                    break;
525                case SEARCH_FOR_INFOS:
526                    writeSearchForInfos(method);
527                    break;
528                case GET_TYPE:
529                    writeGetType(method);
530                    break;
531                case GET_TYPES:
532                    writeGetTypes(method);
533                    break;
534                case RICE_GET_BY_NAMESPACE_AND_NAME:
535                    writeRiceGetByNamespaceAndName(method);
536                    break;
537                default:
538                    writeThrowsNotImplemented(method);
539            }
540            closeBrace();
541        }
542
543        closeBrace();
544
545        this.writeJavaClassAndImportsOutToFile();
546        this.getOut().close();
547    }
548
549    private String getInvalidParameterException() {
550        if (this.isRice()) {
551            return "RiceIllegalArgumentException";
552        }
553        return "InvalidParameterException";
554    }
555
556    private String getOperationFailedException() {
557        if (this.isRice()) {
558            return "RiceIllegalArgumentException";
559        }
560        return "OperationFailedException";
561    }
562
563    private String getDoesNotExistException() {
564        if (this.isRice()) {
565            return "RiceIllegalArgumentException";
566        }
567        return "DoesNotExistException";
568    }
569
570    private String getVersionMismatchException() {
571        if (this.isRice()) {
572            return "RiceIllegalStateException";
573        }
574        return "VersionMismatchException";
575    }
576
577    private void writeThrowsNotImplemented(ServiceMethod method) {
578        indentPrintln("throw new " + this.getOperationFailedException() + " (\"" + method.getName() + " has not been implemented\");");
579    }
580
581    protected String initLower(String str) {
582        return str.substring(0, 1).toLowerCase() + str.substring(1);
583    }
584
585    private String calcCriteriaLookupServiceVariableName(ServiceMethod method) {
586        String objectName = calcObjectName(method);
587        String variableName = initLower(objectName) + "CriteriaLookupService";
588        return variableName;
589    }
590
591    private void writeValidate(ServiceMethod method) {
592        indentPrintln("return new ArrayList<ValidationResultInfo> ();");
593        this.importsAdd(ArrayList.class.getName());
594    }
595    private final Set<String> jpaEntitiesWritten = new HashSet<String>();
596
597    private void writeDaoVariable(ServiceMethod method) {
598        String objectName = calcObjectName(method);
599        String className = objectName + "Dao";
600        String variableName = calcDaoVariableName(method);
601        if (jpaEntitiesWritten.add(variableName)) {
602            XmlType xmlType = finder.findXmlType(objectName + "Info");
603            if (xmlType == null) {
604                System.out.println("Cannot write DAO because the object does not follow patterns.  ObjectName="
605                        + objectName + " method=" + method);
606                return;
607            }
608            new JpaEntityWriter(model, directory, rootPackage, servKey, methods, xmlType, isR1).write();
609            new JpaDaoWriter(model, directory, rootPackage, servKey, methods, xmlType, isR1).write();
610            println("");
611            indentPrintln("private " + className + " " + variableName + ";");
612            println("");
613            indentPrintln("public void set" + className + "(" + className + " " + variableName + ") {");
614            incrementIndent();
615            indentPrintln("this." + variableName + " = " + variableName + ";");
616            decrementIndent();
617            indentPrintln("}");
618            println("");
619            indentPrintln("public " + className + " get" + className + "() {");
620            incrementIndent();
621            indentPrintln("return this." + variableName + ";");
622            decrementIndent();
623            indentPrintln("}");
624
625            variableName = calcCriteriaLookupServiceVariableName(method);
626
627            importsAdd(CriteriaLookupService.class.getName());
628            className = "CriteriaLookupService";
629            println("");
630            indentPrintln("// Criteria Lookup for this object");
631            indentPrintln("private " + className + " " + variableName + ";");
632            println("");
633            indentPrintln("public void set" + initUpper(variableName) + "(" + className + " " + variableName + ") {");
634            incrementIndent();
635            indentPrintln("this." + variableName + " = " + variableName + ";");
636            decrementIndent();
637            indentPrintln("}");
638            println("");
639            indentPrintln("public " + className + " get" + initUpper(variableName) + "() {");
640            incrementIndent();
641            indentPrintln("return this." + variableName + ";");
642            decrementIndent();
643            indentPrintln("}");
644
645        }
646    }
647
648    private void writeCreate(ServiceMethod method) {
649
650//        holdInfo.setPersonId(personId);
651//        holdInfo.setHoldIssueId(issueId);
652//        holdInfo.setTypeKey(holdTypeKey);
653//
654//        HoldIssueEntity holdIssueEntity = holdIssueDao.find(issueId);
655//        if (holdIssueEntity == null) {
656//            throw new InvalidParameterException(issueId);
657//        }
658//        AppliedHoldEntity entity = new AppliedHoldEntity(holdInfo);
659//        entity.setHoldIssue(holdIssueEntity);
660//        entity.setEntityCreated(context);
661//        appliedHoldDao.persist(entity);
662//        appliedHoldDao.getEm().flush();
663//        return entity.toDto();
664        String daoVariable = calcDaoVariableName(method);
665        String entityClassName = calcEntityClassName(method);
666        ServiceMethodParameter typeParam = this.findTypeParameter(method);
667        ServiceMethodParameter infoParam = this.findInfoParameter(method);
668        ServiceMethodParameter contextParam = this.findContextParameter(method);
669        String objectName = calcObjectName(method);
670        String infoName = objectName;
671        if (!this.isRice()) {
672            infoName = infoName + "Info";
673        }
674        String daoName = calcDaoVariableName(method);
675        if (typeParam != null) {
676            indentPrintln(infoParam.getName() + ".setTypeKey (" + typeParam.getName() + ");");
677        }
678        if (method.getParameters().size() > 3) {
679            indentPrintln("//TODO: JPAIMPL overwrite the rest of the readonly fields that are specified on the create in the info object");
680        }
681        indentPrintln(entityClassName + " entity = new " + entityClassName + "(" + infoParam.getName() + ");");
682        indentPrintln("entity.setEntityCreated(" + contextParam.getName() + ");");
683        indentPrintln(daoVariable + ".persist(entity);");
684        indentPrintln(daoVariable + ".getEm().flush();");
685        indentPrintln("return entity.toDto();");
686    }
687
688    private void writeAdd(ServiceMethod method) {
689        indentPrintln("//TODO: JPAIMPL this needs to be implemented");
690        indentPrintln("throw new OperationFailedException (\"Not implemented\");");
691    }
692
693    private ServiceMethodParameter findIdParameter(ServiceMethod method) {
694        String idFieldName = calcObjectName(method) + "Id";
695        for (ServiceMethodParameter parameter : method.getParameters()) {
696            if (parameter.getType().equals("String")) {
697                if (parameter.getName().equals(idFieldName)) {
698                    return parameter;
699                }
700            }
701        }
702
703        // if only one parameter and it is a string then grab that
704        if (method.getParameters().size() == 1) {
705            for (ServiceMethodParameter parameter : method.getParameters()) {
706                if (parameter.getType().equals("String")) {
707                    return parameter;
708                }
709            }
710        }
711        // can't find name exactly 
712        for (ServiceMethodParameter parameter : method.getParameters()) {
713            if (parameter.getType().equals("String")) {
714                if (parameter.getName().endsWith("Id")) {
715                    return parameter;
716                }
717            }
718        }
719        // can't find name exactly try key 
720        for (ServiceMethodParameter parameter : method.getParameters()) {
721            if (parameter.getType().equals("String")) {
722                if (!parameter.getName().endsWith("TypeKey")) {
723                    if (parameter.getName().endsWith("Key")) {
724                        return parameter;
725                    }
726                }
727            }
728        }
729        log.warn("Could not find the Id paramter for {}.{} so returning the first one", method.getService(), method.getName());
730        return method.getParameters().get(0);
731    }
732
733    private ServiceMethodParameter findContextParameter(ServiceMethod method) {
734        for (ServiceMethodParameter parameter : method.getParameters()) {
735            if (parameter.getType().equals("ContextInfo")) {
736                return parameter;
737            }
738        }
739        return null;
740    }
741
742    private ServiceMethodParameter findInfoParameter(ServiceMethod method) {
743        String objectName = calcObjectName(method);
744        if (!this.isRice()) {
745            objectName = objectName + "Info";
746        }
747        for (ServiceMethodParameter parameter : method.getParameters()) {
748            if (parameter.getType().equals(objectName)) {
749                return parameter;
750            }
751        }
752        if (method.getParameters().size() >= 1) {
753            return method.getParameters().get(0);
754        }
755        return null;
756    }
757
758    private ServiceMethodParameter findTypeParameter(ServiceMethod method) {
759        for (ServiceMethodParameter parameter : method.getParameters()) {
760            if (parameter.getType().equals("String")) {
761                if (parameter.getName().endsWith("TypeKey")) {
762                    return parameter;
763                }
764                if (parameter.getName().endsWith("Type")) {
765                    return parameter;
766                }
767            }
768        }
769        return null;
770    }
771
772    private String calcDaoVariableName(ServiceMethod method) {
773        String daoVariableName = this.calcObjectName(method);
774        daoVariableName = this.initLower(daoVariableName) + "Dao";
775        return daoVariableName;
776    }
777
778    private String calcEntityClassName(ServiceMethod method) {
779        String objectName = this.calcObjectName(method);
780        objectName = objectName + "Entity";
781        return objectName;
782    }
783
784    protected String calcObjectName(ServiceMethod method) {
785        if (method.getName().startsWith("create")) {
786            return method.getName().substring("create".length());
787        }
788        if (method.getName().startsWith("update")) {
789            return method.getName().substring("update".length());
790        }
791        if (method.getName().startsWith("validate")) {
792            return method.getName().substring("validate".length());
793        }
794        if (method.getName().startsWith("delete")) {
795            return method.getName().substring("delete".length());
796        }
797        if (method.getName().startsWith("get")) {
798            if (method.getReturnValue().getType().equals("StringList")) {
799                if (method.getName().contains("IdsBy")) {
800                    return method.getName().substring("get".length(),
801                            method.getName().indexOf("IdsBy"));
802                }
803                if (method.getName().contains("KeysBy")) {
804                    return method.getName().substring("get".length(),
805                            method.getName().indexOf("KeysBy"));
806                }
807                if (method.getName().contains("IdsFor")) {
808                    return method.getName().substring("get".length(),
809                            method.getName().indexOf("IdsFor"));
810                }
811                if (method.getName().contains("With")) {
812                    return method.getName().substring("get".length(),
813                            method.getName().indexOf("With"));
814                }
815                if (method.getName().contains("By")) {
816                    return method.getName().substring("get".length(),
817                            method.getName().indexOf("By"));
818                }
819                return method.getName().substring("get".length());
820            }
821            String name = method.getReturnValue().getType();
822            if (name.endsWith("List")) {
823                name = name.substring(0, name.length() - "List".length());
824            }
825            if (name.endsWith("Info")) {
826                name = name.substring(0, name.length() - "Info".length());
827            }
828            return name;
829        }
830
831        if (method.getName().startsWith("searchFor")) {
832            if (method.getReturnValue().getType().equals("StringList")) {
833                if (method.getName().endsWith("Ids")) {
834                    return method.getName().substring("searchFor".length(),
835                            method.getName().indexOf("Ids"));
836                }
837                if (method.getName().endsWith("Keys")) {
838                    return method.getName().substring("get".length(),
839                            method.getName().indexOf("Keys"));
840                }
841                return method.getName().substring("searchFor".length());
842            }
843            String name = method.getReturnValue().getType();
844            if (name.endsWith("List")) {
845                name = name.substring(0, name.length() - "List".length());
846            }
847            if (name.endsWith("Info")) {
848                name = name.substring(0, name.length() - "Info".length());
849            }
850            return name;
851        }
852        if (method.getName().startsWith("add")) {
853            return method.getName().substring("add".length());
854        }
855        if (method.getName().startsWith("remove")) {
856            return method.getName().substring("remove".length());
857        }
858        String returnType = this.stripList(method.getReturnValue().getType());
859        XmlType type = this.finder.findXmlType(returnType);
860        if (type.getPrimitive().equals(XmlType.COMPLEX)) {
861            return returnType;
862        }
863        throw new IllegalArgumentException(method.getName());
864    }
865
866    private void writeUpdate(ServiceMethod method) {
867//        if (!holdId.equals(holdInfo.getId())) {
868//            throw new InvalidParameterException(holdId + " does not match the id in the object " + holdInfo.getId());
869//        }
870//        AppliedHoldEntity entity = appliedHoldDao.find(holdId);
871//        if (null == entity) {
872//            throw new DoesNotExistException(holdId);
873//        }
874//        entity.fromDto(holdInfo);
875//        entity.setEntityUpdated(context);
876//        entity = appliedHoldDao.merge(entity);
877//        appliedHoldDao.getEm().flush(); // need to flush to get the version indicator updated
878//        return entity.toDto();
879        ServiceMethodParameter idParam = this.findIdParameter(method);
880        ServiceMethodParameter infoParam = this.findInfoParameter(method);
881        ServiceMethodParameter contextParam = this.findContextParameter(method);
882        String daoVariable = calcDaoVariableName(method);
883        String entityClassName = calcEntityClassName(method);
884        if (infoParam == null) {
885            throw new NullPointerException(method.getName());
886        }
887        String objectName = calcObjectName(method);
888        String infoName = objectName;
889        if (!this.isRice()) {
890            infoName = infoName + "Info";
891        }
892        if (idParam != null) {
893            if (!this.isRice()) {
894                indentPrintln("if (!" + idParam.getName() + ".equals (" + infoParam.getName() + ".getId())) {");
895                indentPrintln("    throw new " + this.getInvalidParameterException() + " (\"The id parameter does not match the id on the info object\");");
896                indentPrintln("}");
897            }
898        }
899        indentPrintln(entityClassName + " entity = " + daoVariable + ".find(" + idParam.getName() + ");");
900        indentPrintln("if (entity == null) {");
901        incrementIndent();
902        indentPrintln("throw new DoesNotExistException(" + idParam.getName() + ");");
903        decrementIndent();
904        indentPrintln("}");
905        indentPrintln("entity.fromDto(" + infoParam.getName() + ");");
906        indentPrintln("entity.setEntityUpdated(" + contextParam.getName() + ");");
907        indentPrintln("entity = " + daoVariable + ".merge(entity);");
908        indentPrintln("return entity.toDto();");
909    }
910
911    private void writeDelete(ServiceMethod method) {
912//        AppliedHoldEntity entity = appliedHoldDao.find(holdId);
913//        if (null == entity) {
914//            throw new DoesNotExistException(holdId);
915//        }
916//        appliedHoldDao.remove(entity);
917//        StatusInfo status = new StatusInfo();
918//        status.setSuccess(Boolean.TRUE);
919//        return status;
920        ServiceMethodParameter idParam = this.findIdParameter(method);
921        String daoVariable = calcDaoVariableName(method);
922        String entityClassName = calcEntityClassName(method);
923        indentPrintln(entityClassName + " entity = " + daoVariable + ".find(" + idParam.getName() + ");");
924        indentPrintln("if (entity == null) {");
925        incrementIndent();
926        indentPrintln("throw new DoesNotExistException(" + idParam.getName() + ");");
927        decrementIndent();
928        indentPrintln("}");
929        indentPrintln(daoVariable + ".remove(entity);");
930        indentPrintln("StatusInfo status = new StatusInfo();");
931        importsAdd("org.kuali.student.r2.common.dto.StatusInfo");
932        indentPrintln("status.setSuccess(Boolean.TRUE);");
933        indentPrintln("return status;");
934    }
935
936    private void writeRemove(ServiceMethod method) {
937        indentPrintln("//TODO: JPAIMPL this needs to be implemented");
938        indentPrintln("throw new OperationFailedException (\"Not implemented\");");
939    }
940
941    private void writeGetById(ServiceMethod method) {
942//        AppliedHoldEntity entity = appliedHoldDao.find(holdId);
943//        if (entity == null) {
944//            throw new DoesNotExistException(holdId);
945//        }
946//        return entity.toDto();
947        ServiceMethodParameter idParam = this.findIdParameter(method);
948        String daoVariable = calcDaoVariableName(method);
949        String entityClassName = calcEntityClassName(method);
950        indentPrintln(entityClassName + " entity = " + daoVariable + ".find(" + idParam.getName() + ");");
951        indentPrintln("if (entity == null) {");
952        incrementIndent();
953        indentPrintln("throw new DoesNotExistException(" + idParam.getName() + ");");
954        decrementIndent();
955        indentPrintln("}");
956        indentPrintln("return entity.toDto();");
957    }
958
959    private void writeGetByIds(ServiceMethod method) {
960//        List<HoldIssueEntity> holdIssues = holdIssueDao.findByIds(issueIds);
961//        List<HoldIssueInfo> result = new ArrayList<HoldIssueInfo>(holdIssues.size());
962//        for (HoldIssueEntity entity : holdIssues) {
963//            if (entity == null) {
964//                // if one of the entities from "findByIds" is returned as null, then one of the keys in the list was not found
965//                throw new DoesNotExistException();
966//            }
967//            result.add(entity.toDto());
968//        }
969//        return result;
970        String daoVariable = calcDaoVariableName(method);
971        String entityClassName = calcEntityClassName(method);
972        String objectName = this.calcObjectName(method);
973        ServiceMethodParameter idListParam = this.findIdListParameter(method);
974        String infoName = objectName;
975        infoName = infoName + "Info";
976        this.importsAdd(ArrayList.class.getName());
977        indentPrintln("List<" + entityClassName + "> entities = " + daoVariable + ".findByIds(" + idListParam.getName() + ");");
978        indentPrintln("List<" + infoName + "> list = new ArrayList<" + infoName + "> (entities.size());");
979        indentPrintln("for (" + entityClassName + " entity : entities) {");
980        incrementIndent();
981        indentPrintln("if (entity == null) {");
982        incrementIndent();
983        indentPrintln("throw new DoesNotExistException();");
984        decrementIndent();
985        indentPrintln("}");
986        indentPrintln("list.add(entity.toDto());");
987        decrementIndent();
988        indentPrintln("}");
989        indentPrintln("return list;");
990    }
991
992    private String calcNamedQuery(ServiceMethod method) {
993        String objectName = calcObjectName(method);
994        String name = method.getName();
995        if (name.startsWith("get")) {
996            name = name.substring("get".length());
997        }
998        if (name.startsWith(objectName)) {
999            name = name.substring(objectName.length());
1000        }
1001//        if (name.startsWith("Ids")) {
1002//            name = name.substring("Ids".length());
1003//        }
1004//        if (name.isEmpty()) {
1005//            throw new RuntimeException (method.getName());
1006//        }
1007        // remove plural
1008        if (!method.getReturnValue().getType().equals("StringList")) {
1009            if (name.startsWith("s")) {
1010                name = name.substring("s".length());
1011            }
1012        }
1013        // add back the get
1014        name = "get" + name;
1015        return name;
1016    }
1017
1018    private void writeGetIdsByOther(ServiceMethod method) {
1019//      return appliedHoldDao.getIdsByIssue(issueId);
1020        String objectName = this.calcObjectName(method);
1021        String infoName = objectName;
1022        if (!this.isRice()) {
1023            infoName = infoName + "Info";
1024        }
1025        String daoVariableName = calcDaoVariableName(method);
1026        String namedQuery = calcNamedQuery(method);
1027        indentPrint("return " + daoVariableName + "." + namedQuery + "(");
1028        String comma = "";
1029        for (ServiceMethodParameter param : method.getParameters()) {
1030            if (param.getType().equals("ContextInfo")) {
1031                continue;
1032            }
1033            print(comma);
1034            comma = ", ";
1035            print(param.getName());
1036        }
1037        println(");");
1038    }
1039
1040    private ServiceMethodParameter findCriteriaParam(ServiceMethod method) {
1041        for (ServiceMethodParameter param : method.getParameters()) {
1042            if (param.getType().equals("QueryByCriteria")) {
1043                return param;
1044            }
1045        }
1046        return null;
1047    }
1048
1049    private void writeSearchForIds(ServiceMethod method) {
1050//        List<String> results = new ArrayList<String>();
1051//        GenericQueryResults<AppliedHoldEntity> appliedHolds = criteriaLookupService.lookup(AppliedHoldEntity.class, criteria);
1052//        if (null != appliedHolds && appliedHolds.getResults().size() > 0) {
1053//            for (AppliedHoldEntity appliedHold : appliedHolds.getResults()) {
1054//                results.add(appliedHold.getId());
1055//            }
1056//        }
1057//        return results;
1058        String objectName = this.calcObjectName(method);
1059        String infoName = objectName;
1060        if (!this.isRice()) {
1061            infoName = infoName + "Info";
1062        }
1063        String criteriaLookupVariableName = calcCriteriaLookupServiceVariableName(method);
1064        String entityClassName = this.calcEntityClassName(method);
1065        ServiceMethodParameter criteriaParam = findCriteriaParam(method);
1066        importsAdd(CriteriaLookupService.class.getName());
1067        importsAdd(ArrayList.class.getName());
1068        importsAdd(List.class.getName());
1069        indentPrintln("List<String> results = new ArrayList<String>();");
1070        importsAdd(GenericQueryResults.class.getName());
1071        indentPrintln("GenericQueryResults<" + entityClassName + "> entities");
1072        indentPrintln("    = " + criteriaLookupVariableName + ".lookup(" + entityClassName + ".class, " + criteriaParam.getName() + ");");
1073        indentPrintln("if (null != entities && !entities.getResults().isEmpty()) {");
1074        incrementIndent();
1075        indentPrintln("for (" + entityClassName + " entity : entities.getResults()) {");
1076        incrementIndent();
1077        indentPrintln("results.add(entity.getId());");
1078        decrementIndent();
1079        indentPrintln("}");
1080        decrementIndent();
1081        indentPrintln("}");
1082        indentPrintln("return results;");
1083    }
1084
1085    private void writeSearchForInfos(ServiceMethod method) {
1086//        List<AppliedHoldInfo> results = new ArrayList<AppliedHoldInfo>();
1087//        GenericQueryResults<AppliedHoldEntity> appliedHolds = criteriaLookupService.lookup(AppliedHoldEntity.class, criteria);
1088//        if (null != appliedHolds && appliedHolds.getResults().size() > 0) {
1089//            for (AppliedHoldEntity appliedHold : appliedHolds.getResults()) {
1090//                results.add(appliedHold.toDto());
1091//            }
1092//        }
1093//        return results;
1094        String objectName = this.calcObjectName(method);
1095        String criteriaLookupVariableName = calcCriteriaLookupServiceVariableName(method);
1096        String entityClassName = this.calcEntityClassName(method);
1097        String infoName = this.calcInfoName(method);
1098        ServiceMethodParameter criteriaParam = findCriteriaParam(method);
1099        importsAdd(CriteriaLookupService.class.getName());
1100        importsAdd(ArrayList.class.getName());
1101        importsAdd(List.class.getName());
1102        indentPrintln("List<" + infoName + "> results = new ArrayList<" + infoName + ">();");
1103        importsAdd(GenericQueryResults.class.getName());
1104        indentPrintln("GenericQueryResults<" + entityClassName + "> entities");
1105        indentPrintln("    = " + criteriaLookupVariableName + ".lookup(" + entityClassName + ".class, " + criteriaParam.getName() + ");");
1106        indentPrintln("if (null != entities && !entities.getResults().isEmpty()) {");
1107        incrementIndent();
1108        indentPrintln("for (" + entityClassName + " entity : entities.getResults()) {");
1109        incrementIndent();
1110        indentPrintln("results.add(entity.toDto());");
1111        decrementIndent();
1112        indentPrintln("}");
1113        decrementIndent();
1114        indentPrintln("}");
1115        indentPrintln("return results;");
1116    }
1117
1118    private ServiceMethodParameter getTypeParameter(ServiceMethod method) {
1119        ServiceMethodParameter fallbackParam = null;
1120        for (ServiceMethodParameter parameter : method.getParameters()) {
1121            if (parameter.getName().endsWith("TypeKey")) {
1122                return parameter;
1123            }
1124            if (parameter.getType().equals("String")) {
1125                if (parameter.getName().toLowerCase().contains("type")) {
1126                    fallbackParam = parameter;
1127                }
1128            }
1129        }
1130        return fallbackParam;
1131    }
1132
1133    private String calcInfoName(ServiceMethod method) {
1134        String objectName = this.calcObjectName(method);
1135        String infoName = objectName;
1136        if (!this.isRice()) {
1137            infoName = infoName + "Info";
1138        }
1139        return infoName;
1140    }
1141
1142    private void writeGetIdsByType(ServiceMethod method) {
1143        String objectName = this.calcObjectName(method);
1144        String infoName = this.calcInfoName(method);
1145        String daoVariable = calcDaoVariableName(method);
1146        ServiceMethodParameter typeParam = this.getTypeParameter(method);
1147        if (typeParam == null) {
1148
1149        }
1150        indentPrintln("return " + daoVariable + ".getIdsByType(" + typeParam.getName() + ");");
1151    }
1152
1153    private void writeRiceGetByNamespaceAndName(ServiceMethod method) {
1154        indentPrintln("//TODO: JPAIMPL this needs to be implemented");
1155        indentPrintln("throw new OperationFailedException (\"Not implemented\");");
1156    }
1157
1158    private void writeGetInfosByOther(ServiceMethod method) {
1159//        List<AppliedHoldEntity> entities = this.appliedHoldDao.getByPerson(personId);
1160//        List<AppliedHoldInfo> result = new ArrayList<AppliedHoldInfo>(entities.size());
1161//        for (AppliedHoldEntity entity : entities) {
1162//            result.add(entity.toDto());
1163//        }
1164//        return result;
1165        String objectName = this.calcObjectName(method);
1166        String infoName = objectName;
1167        if (!this.isRice()) {
1168            infoName = infoName + "Info";
1169        }
1170        String daoVariableName = calcDaoVariableName(method);
1171        String namedQuery = calcNamedQuery(method);
1172        String entityClassName = calcEntityClassName(method);
1173        indentPrint("List<" + entityClassName + "> entities = " + daoVariableName + "." + namedQuery + "(");
1174        String comma = "";
1175        for (ServiceMethodParameter param : method.getParameters()) {
1176            if (param.getType().equals("ContextInfo")) {
1177                continue;
1178            }
1179            print(comma);
1180            comma = ", ";
1181            print(param.getName());
1182        }
1183        println(");");
1184        this.importsAdd(ArrayList.class.getName());
1185        indentPrintln("List<" + infoName + "> list = new ArrayList<" + infoName + "> (entities.size());");
1186        indentPrintln("for (" + entityClassName + " entity: entities) {");
1187        indentPrintln("    list.add (entity.toDto ());");
1188        indentPrintln("}");
1189        indentPrintln("return list;");
1190    }
1191
1192    private void writeGetType(ServiceMethod method) {
1193        indentPrintln("//TODO: JPAIMPL this needs to be implemented");
1194        indentPrintln("throw new OperationFailedException (\"Not implemented\");");
1195    }
1196
1197    private void writeGetTypes(ServiceMethod method) {
1198        indentPrintln("//TODO: JPAIMPL this needs to be implemented");
1199        indentPrintln("throw new OperationFailedException (\"Not implemented\");");
1200    }
1201
1202    private String initUpper(String str) {
1203        return str.substring(0, 1).toUpperCase() + str.substring(1);
1204    }
1205
1206    private ServiceMethodParameter findIdListParameter(ServiceMethod method) {
1207        String idFieldName = calcObjectName(method) + "Ids";
1208        if (this.isRice()) {
1209            idFieldName = "ids";
1210        }
1211        for (ServiceMethodParameter parameter : method.getParameters()) {
1212            if (parameter.getType().equals("StringList")) {
1213                if (parameter.getName().equals(idFieldName)) {
1214                    return parameter;
1215                }
1216            }
1217        }
1218        // can't find name exactly 
1219        for (ServiceMethodParameter parameter : method.getParameters()) {
1220            if (parameter.getType().equals("StringList")) {
1221                if (parameter.getName().endsWith("Ids")) {
1222                    return parameter;
1223                }
1224            }
1225        }
1226        // can't find name exactly try key 
1227        for (ServiceMethodParameter parameter : method.getParameters()) {
1228            if (parameter.getType().equals("StringList")) {
1229                if (parameter.getName().endsWith("Keys")) {
1230                    return parameter;
1231                }
1232            }
1233        }
1234        return null;
1235    }
1236
1237    private String stripList(String str) {
1238        return GetterSetterNameCalculator.stripList(str);
1239    }
1240
1241    private String calcExceptionClassName(ServiceMethodError error) {
1242        if (error.getClassName() == null) {
1243            return ServiceExceptionWriter.calcClassName(error.getType());
1244        }
1245        return error.getClassName();
1246    }
1247
1248    private String calcExceptionPackageName(ServiceMethodError error) {
1249        if (error.getClassName() == null) {
1250            return ServiceExceptionWriter.calcPackage(rootPackage);
1251        }
1252        return error.getPackageName();
1253    }
1254
1255    private String calcType(String type, String realType) {
1256        XmlType t = finder.findXmlType(this.stripList(type));
1257        String retType = MessageStructureTypeCalculator.calculate(this, model, type, realType,
1258                t.getJavaPackage());
1259        if (this.isRice()) {
1260            if (retType.equals("Boolean")) {
1261                retType = "boolean";
1262            }
1263            if (retType.equals("Void")) {
1264                retType = "void";
1265            }
1266        }
1267        return retType;
1268    }
1269}