PageRenderTime 48ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/crank-crud/src/main/java/org/crank/crud/GenericDaoJpaWithoutJpaTemplate.java

http://krank.googlecode.com/
Java | 751 lines | 585 code | 138 blank | 28 comment | 66 complexity | e3551194190cbddf6eccc550f79d65e9 MD5 | raw file
  1. package org.crank.crud;
  2. import java.io.Serializable;
  3. import java.lang.reflect.Method;
  4. import java.util.ArrayList;
  5. import java.util.Arrays;
  6. import java.util.Collection;
  7. import java.util.HashMap;
  8. import java.util.List;
  9. import java.util.Map;
  10. import javax.persistence.EntityManager;
  11. import javax.persistence.EntityManagerFactory;
  12. import javax.persistence.LockModeType;
  13. import javax.persistence.PersistenceContext;
  14. import javax.persistence.PersistenceException;
  15. import javax.persistence.PersistenceUnit;
  16. import javax.persistence.Query;
  17. import org.apache.log4j.Logger;
  18. import org.crank.crud.criteria.Criterion;
  19. import org.crank.crud.criteria.Example;
  20. import org.crank.crud.criteria.Group;
  21. import org.crank.crud.criteria.OrderBy;
  22. import org.crank.crud.criteria.OrderDirection;
  23. import org.crank.crud.criteria.Select;
  24. import org.crank.crud.join.Join;
  25. import org.springframework.dao.InvalidDataAccessApiUsageException;
  26. import org.springframework.dao.UncategorizedDataAccessException;
  27. import org.springframework.orm.jpa.JpaSystemException;
  28. import org.springframework.transaction.annotation.Transactional;
  29. /**
  30. * @param <T>
  31. * Dao class
  32. * @param <PK>
  33. * id class
  34. * @version $Revision:$
  35. * @author Rick Hightower
  36. *
  37. *
  38. */
  39. public class GenericDaoJpaWithoutJpaTemplate<T, PK extends Serializable>
  40. implements GenericDao<T, PK> {
  41. protected Class<T> type = null;
  42. protected boolean distinct = false;
  43. protected Logger logger = Logger.getLogger(GenericDaoJpa.class);
  44. private String newSelectStatement = null;
  45. private List<QueryHint<?>> queryHints;
  46. protected String idPropertyName = null;
  47. @PersistenceContext
  48. protected EntityManager entityManager;
  49. @PersistenceUnit
  50. protected EntityManagerFactory entityManagerFactory;
  51. public void setQueryHints(List<QueryHint<?>> queryHints) {
  52. this.queryHints = queryHints;
  53. }
  54. public void setEntityManager(EntityManager entityManager) {
  55. this.entityManager = entityManager;
  56. }
  57. public void setEntityManagerFactory(
  58. EntityManagerFactory entityManagerFactory) {
  59. this.entityManagerFactory = entityManagerFactory;
  60. }
  61. public EntityManagerFactory getEntityManagerFactory() {
  62. return entityManagerFactory;
  63. }
  64. public EntityManager getEntityManager() {
  65. if (entityManager == null || !entityManager.isOpen()) {
  66. entityManager = getEntityManagerFactory().createEntityManager();
  67. }
  68. return entityManager;
  69. }
  70. public GenericDaoJpaWithoutJpaTemplate(final Class<T> aType) {
  71. this.type = aType;
  72. }
  73. public GenericDaoJpaWithoutJpaTemplate() {
  74. }
  75. public void setIsDistinct(boolean isDistinct) {
  76. this.distinct = isDistinct;
  77. }
  78. public void setDistinct(boolean isDistinct) {
  79. this.distinct = isDistinct;
  80. }
  81. public boolean isDistinct() {
  82. return this.distinct;
  83. }
  84. @Transactional
  85. public T store(T entity) {
  86. logger.debug(String.format("store(entity) called, %s", entity));
  87. T persistedEntity = entity;
  88. /*
  89. * If the entity has a null id, then use the persist method,
  90. * otherwise use the merge method.
  91. */
  92. if (!hasId(entity)) {
  93. // TODO: the error reporting could be deferred (the entity has an ID
  94. // that is in the db but not in the entity manager)
  95. logger.debug("Calling perist on JPA");
  96. getEntityManager().persist(entity);
  97. } else {
  98. logger.debug("Calling merge since an id was found");
  99. persistedEntity = (T) getEntityManager().merge(entity);
  100. }
  101. return persistedEntity;
  102. }
  103. protected boolean hasId(T entity) {
  104. return GenericDaoUtils.hasId(entity, this.getIdPropertyName());
  105. }
  106. @Transactional
  107. public void persist(T entity) {
  108. getEntityManager().persist(entity);
  109. }
  110. @Transactional
  111. public T merge(T entity) {
  112. return getEntityManager().merge(entity);
  113. }
  114. @Transactional
  115. public <RE> RE mergeRelated(RE entity) {
  116. return getEntityManager().merge(entity);
  117. }
  118. @Transactional
  119. public void delete(final PK id) {
  120. String queryString = "DELETE FROM " + getEntityName() + " WHERE "
  121. + getIdPropertyName() + " = " + id;
  122. Query query = getEntityManager().createQuery(queryString);
  123. query.executeUpdate();
  124. }
  125. public void delete(T entity) {
  126. T managedEntity = entity;
  127. if (!getEntityManager().contains(entity)) {
  128. managedEntity = getEntityManager().merge(entity);
  129. }
  130. getEntityManager().remove(managedEntity);
  131. }
  132. public T read(PK id) {
  133. if (type == null) {
  134. throw new UnsupportedOperationException(
  135. "The type must be set to use this method.");
  136. }
  137. return read(type, id);
  138. }
  139. @SuppressWarnings("unchecked")
  140. public T read(Class clazz, PK id) {
  141. return (T) getEntityManager().find(clazz, id);
  142. }
  143. public T readExclusive(PK id) {
  144. if (type == null) {
  145. throw new UnsupportedOperationException(
  146. "The type must be set to use this method.");
  147. }
  148. return readExclusive(type, id);
  149. }
  150. @SuppressWarnings("unchecked")
  151. public T readExclusive(Class clazz, PK id) {
  152. Object entity = (T) getEntityManager().find(clazz, id);
  153. getEntityManager().lock(entity, LockModeType.READ);
  154. return (T) entity;
  155. }
  156. public T refresh(final T transientObject) {
  157. EntityManager em = getEntityManager();
  158. T managedEntity = null;
  159. if (em.contains(transientObject)) {
  160. managedEntity = transientObject;
  161. }
  162. else {
  163. managedEntity = em.merge(transientObject);
  164. }
  165. // now refresh the state of the managed object
  166. em.refresh(managedEntity);
  167. return managedEntity;
  168. }
  169. public T refresh(final PK id) {
  170. if (type == null) {
  171. throw new UnsupportedOperationException(
  172. "The type must be set to use this method.");
  173. }
  174. EntityManager em = getEntityManager();
  175. T managedEntity = em.find(this.type, id);
  176. em.refresh(managedEntity);
  177. return managedEntity;
  178. }
  179. public void flushAndClear() {
  180. EntityManager entityManager = getEntityManager();
  181. entityManager.flush();
  182. entityManager.clear();
  183. }
  184. /**
  185. * @deprecated use merge
  186. */
  187. @Transactional
  188. public void create(final T newInstance) {
  189. getEntityManager().persist(newInstance);
  190. }
  191. /**
  192. * @deprecated use merge
  193. */
  194. @Transactional
  195. public T update(final T transientObject) {
  196. return getEntityManager().merge(transientObject);
  197. }
  198. public void setType(final Class<T> aType) {
  199. this.type = aType;
  200. }
  201. public List<T> searchOrdered(Criterion criteria, String... orderBy) {
  202. return this.find(orderBy, criteria);
  203. }
  204. public List<T> searchOrdered(Class<T> clazz, Criterion criteria,
  205. String... orderBy) {
  206. return this.find(clazz, orderBy, criteria);
  207. }
  208. public List<T> find(List<Criterion> criteria, List<String> orderBy) {
  209. return find(type, criteria, orderBy);
  210. }
  211. public List<T> find(Class<T> clazz, List<Criterion> criteria,
  212. List<String> orderBy) {
  213. return find(clazz, orderBy.toArray(new String[orderBy.size()]),
  214. (Criterion[]) criteria.toArray(new Criterion[criteria.size()]));
  215. }
  216. public List<T> find(List<Criterion> criteria, String[] orderBy) {
  217. return find(type, criteria, orderBy);
  218. }
  219. public List<T> find(Class<T> clazz, List<Criterion> criteria, String[] orderBy) {
  220. return find(clazz, orderBy, (Criterion[]) criteria
  221. .toArray(new Criterion[criteria.size()]));
  222. }
  223. public List<T> find(Map<String, Object> propertyValues) {
  224. return find(propertyValues, null);
  225. }
  226. public List<T> find(Class<T> clazz, Map<String, Object> propertyValues) {
  227. return find(clazz, propertyValues, null);
  228. }
  229. public List<T> find(T example) {
  230. return find(Example.like(example));
  231. }
  232. public List<T> find(Map<String, Object> propertyValues, String[] orderBy) {
  233. return find(orderBy, Group.and(propertyValues));
  234. }
  235. public List<T> find(Class<T> clazz, Map<String, Object> propertyValues,
  236. String[] orderBy) {
  237. return find(clazz, orderBy, Group.and(propertyValues));
  238. }
  239. public List<T> find(String property, Object value) {
  240. return find(type, property, value);
  241. }
  242. public List<T> find(Class<T> clazz, String property, Object value) {
  243. HashMap<String, Object> propertyValues = new HashMap<String, Object>();
  244. propertyValues.put(property, value);
  245. return find(clazz, propertyValues);
  246. }
  247. public List<T> find() {
  248. return find(type);
  249. }
  250. public int count() {
  251. Query query = createCountQuery(getEntityManager());
  252. prepareQueryHintsIfNeeded(query);
  253. Number count = (Number) query.getSingleResult();
  254. return count.intValue();
  255. }
  256. private Query createCountQuery(EntityManager em) {
  257. Query query = null;
  258. try {
  259. query = em.createNamedQuery(getEntityName() + ".countAll");
  260. logger
  261. .debug("using native countAll query for entity "
  262. + getEntityName());
  263. } catch (IllegalArgumentException iae) {
  264. // thrown if a query has not been defined with the given name
  265. query = em.createQuery("SELECT count(*) FROM " + getEntityName()
  266. + " instance");
  267. logger.debug("using JPA countAll query for entity " + getEntityName());
  268. } catch (PersistenceException pe) {
  269. // JPA spec says IllegalArgumentException should be thrown, yet
  270. // hibernate throws PersistenceException instead
  271. query = em.createQuery("SELECT count(*) FROM " + getEntityName()
  272. + " instance");
  273. logger.debug("using JPA countAll query for entity " + getEntityName());
  274. }
  275. return query;
  276. }
  277. @SuppressWarnings("unchecked")
  278. public List<T> find(Class<T> clazz) {
  279. String entityName = getEntityName();
  280. String sQuery = null;
  281. if (newSelectStatement == null) {
  282. sQuery = "SELECT instance FROM " + entityName + " instance";
  283. } else {
  284. sQuery = "SELECT " + newSelectStatement + " FROM " + entityName + " o";
  285. }
  286. Query query = getEntityManager().createQuery(sQuery);
  287. prepareQueryHintsIfNeeded(query);
  288. return (List<T>) query.getResultList();
  289. }
  290. public List<T> find(String[] propertyNames, Object[] values) {
  291. return find(propertyNames, values, null);
  292. }
  293. public List<T> find(Class<T> clazz, String[] propertyNames, Object[] values) {
  294. return find(clazz, propertyNames, values, null);
  295. }
  296. public List<T> find(Criterion... criteria) {
  297. return find((String[]) null, criteria);
  298. }
  299. public int count(final Criterion... criteria) {
  300. if (criteria == null || criteria.length == 0) {
  301. // count all if no criteria specified
  302. return count();
  303. }
  304. if (logger.isDebugEnabled()) {
  305. logger.debug("count called with Criteria " + criteria);
  306. }
  307. final Group group = criteria != null ? Group.and(criteria) : null;
  308. final String squery = CriteriaUtils.createCountQuery(group, this.type, this.distinct);
  309. return executeCountQuery(group, squery, criteria);
  310. }
  311. public List<T> find(Class<T> clazz, Criterion... criteria) {
  312. return find(clazz, (String[]) null, criteria);
  313. }
  314. public List<T> find(Class<T> clazz, String[] propertyNames, Object[] values,
  315. String[] orderBy) {
  316. if (propertyNames.length != values.length) {
  317. throw new RuntimeException(
  318. "You are not using this API correctly. The propertynames length should always match values length.");
  319. }
  320. Map<String, Object> propertyValues = new HashMap<String, Object>(
  321. propertyNames.length);
  322. int index = 0;
  323. for (String propertyName : propertyNames) {
  324. propertyValues.put(propertyName, values[index]);
  325. index++;
  326. }
  327. return find(clazz, propertyValues, orderBy);
  328. }
  329. public List<T> find(String[] propertyNames, Object[] values,
  330. String[] orderBy) {
  331. return find(type, propertyNames, values, orderBy);
  332. }
  333. public List<T> find(Join[] fetches, String[] orderBy,
  334. Criterion... criteria) {
  335. return doFind(this.type, orderBy, criteria, fetches);
  336. }
  337. public List<T> find(Join[] fetches, Criterion... criteria) {
  338. return doFind(this.type, null, criteria, fetches);
  339. }
  340. public List<T> find(Join... fetches) {
  341. return doFind(this.type, null, null, fetches);
  342. }
  343. public List<T> find(Join[] fetches, String[] orderBy, int startPosition,
  344. int maxResults, Criterion... criteria) {
  345. return doFind(this.type, orderBy, criteria, fetches, startPosition,
  346. maxResults);
  347. }
  348. public List<T> find(String[] orderBy, int startPosition, int maxResults,
  349. Criterion... criteria) {
  350. return doFind(this.type, orderBy, criteria, null, startPosition,
  351. maxResults);
  352. }
  353. public List<T> find(int startPosition, int maxResults,
  354. Criterion... criteria) {
  355. return doFind(this.type, (OrderBy[]) null, criteria, null,
  356. startPosition, maxResults);
  357. }
  358. public List<T> find(int startPosition, int maxResults) {
  359. return doFind(this.type, (OrderBy[]) null, null, null, startPosition,
  360. maxResults);
  361. }
  362. public List<T> find(Join[] fetches, OrderBy[] orderBy, int startPosition,
  363. int maxResults, Criterion... criteria) {
  364. return doFind(this.type, orderBy, criteria, fetches, startPosition,
  365. maxResults);
  366. }
  367. public List<T> find(OrderBy[] orderBy, int startPosition, int maxResults,
  368. Criterion... criteria) {
  369. return doFind(this.type, orderBy, criteria, null, startPosition,
  370. maxResults);
  371. }
  372. public List<T> find(OrderBy[] orderBy, Criterion... criteria) {
  373. return doFind(this.type, orderBy, criteria, null, -1, -1);
  374. }
  375. public List<T> find(Class<T> clazz, String[] orderBy, Criterion... criteria) {
  376. return doFind(clazz, orderBy, criteria, null);
  377. }
  378. private List<T> doFind(Class<T> clazz, OrderBy[] orderBy,
  379. final Criterion[] criteria, Join[] fetches,
  380. final int startPosition, final int maxResult) {
  381. return doFind(clazz, (Select[])null, this.distinct, orderBy, criteria, fetches, startPosition,
  382. maxResult);
  383. }
  384. @SuppressWarnings("unchecked")
  385. private List<T> doFind(Class<T> clazz, Select[] selects, boolean distinctFlag, OrderBy[] orderBy,
  386. final Criterion[] criteria, Join[] joins, final int startPosition, final int maxResult) {
  387. final Group group = criteria != null ? Group.and(criteria)
  388. : new Group();
  389. final String sQuery = CriteriaUtils.createQuery(clazz, selects, this.newSelectStatement, distinctFlag,
  390. orderBy, joins, group);
  391. return (List<T>)executeQueryWithJPA(criteria, startPosition, maxResult, group,
  392. sQuery);
  393. }
  394. private List<T> doFind(Class<T> clazz, String[] orderBy,
  395. final Criterion[] criteria, Join[] fetches,
  396. final int startPosition, final int maxResult) {
  397. if (orderBy != null) {
  398. List<OrderBy> list = new ArrayList<OrderBy>();
  399. for (String order : orderBy) {
  400. list.add(new OrderBy(order, OrderDirection.ASC));
  401. }
  402. return doFind(clazz, list.toArray(new OrderBy[orderBy.length]),
  403. criteria, fetches, startPosition, maxResult);
  404. } else {
  405. return doFind(clazz, (OrderBy[]) null, criteria, fetches,
  406. startPosition, maxResult);
  407. }
  408. }
  409. private List<T> doFind(Class<T> clazz, String[] orderBy, Criterion[] criteria,
  410. Join[] fetches) {
  411. return doFind(clazz, orderBy, criteria, fetches, -1, -1);
  412. }
  413. public List<T> find(String[] orderBy, Criterion... criteria) {
  414. return find(type, orderBy, criteria);
  415. }
  416. protected String getEntityName() {
  417. if (type == null) {
  418. throw new UnsupportedOperationException(
  419. "The type must be set to use this method.");
  420. }
  421. return GenericDaoUtils.getEntityName(type);
  422. }
  423. public Object executeFinder(final Method method, final Object[] queryArgs) {
  424. final String queryName = queryNameFromMethod(method);
  425. Query query = getEntityManager().createNamedQuery(queryName);
  426. int index = 1;
  427. for (Object arg : queryArgs) {
  428. query.setParameter(index, arg);
  429. index++;
  430. }
  431. if (List.class.isAssignableFrom(method.getReturnType())) {
  432. return query.getResultList();
  433. } else {
  434. return query.getSingleResult();
  435. }
  436. }
  437. public T readPopulated(Class<T> clazz, final PK id) {
  438. try {
  439. return doReadPopulated(id);
  440. } catch (JpaSystemException jpaSystemException) {
  441. return read(id);
  442. }
  443. }
  444. @SuppressWarnings("unchecked")
  445. private T doReadPopulated(final PK id) {
  446. final String queryName = type.getSimpleName() + ".readPopulated";
  447. try {
  448. Query query = getEntityManager().createNamedQuery(queryName);
  449. query.setParameter(1, id);
  450. return (T) query.getSingleResult();
  451. } catch (Exception ex) {
  452. return read(type, id);
  453. }
  454. }
  455. public T readPopulated(final PK id) {
  456. try {
  457. return doReadPopulated(id);
  458. } catch (JpaSystemException jpaSystemException) {
  459. return read(id);
  460. } catch (IllegalArgumentException iae) {
  461. return read(id);
  462. } catch (InvalidDataAccessApiUsageException idaaue) {
  463. return read(id);
  464. } catch (UncategorizedDataAccessException udae) {
  465. return read(id);
  466. }
  467. }
  468. public String queryNameFromMethod(Method finderMethod) {
  469. return getEntityName() + "." + finderMethod.getName();
  470. }
  471. public void delete(Collection<T> entities) {
  472. for (T entity : entities) {
  473. delete(entity);
  474. }
  475. }
  476. public Collection<T> merge(Collection<T> entities) {
  477. List<T> results = new ArrayList<T>();
  478. for (T entity : entities) {
  479. results.add(merge(entity));
  480. }
  481. return results;
  482. }
  483. @Transactional
  484. public <RE> Collection<RE> mergeRelated(Collection<RE> entities) {
  485. Collection<RE> mergedResults = new ArrayList<RE>(entities.size());
  486. for (RE entity : entities) {
  487. mergedResults.add(mergeRelated(entity));
  488. }
  489. return mergedResults;
  490. }
  491. public void persist(Collection<T> entities) {
  492. for (T entity : entities) {
  493. persist(entity);
  494. }
  495. }
  496. public Collection<T> refresh(Collection<T> entities) {
  497. Collection<T> refreshedResults = new ArrayList<T>(entities.size());
  498. for (T entity : entities) {
  499. refreshedResults.add(refresh(entity));
  500. }
  501. return refreshedResults;
  502. }
  503. public Collection<T> store(Collection<T> entities) {
  504. List<T> results = new ArrayList<T>();
  505. for (T entity : entities) {
  506. results.add(store(entity));
  507. }
  508. return results;
  509. }
  510. @Transactional
  511. public void run(Runnable runnable) {
  512. runnable.run();
  513. }
  514. public int count(Join[] joins, final Criterion... criteria) {
  515. if ((joins == null || joins.length == 0) &&
  516. (criteria == null || criteria.length == 0) )
  517. {
  518. // count all if no joins or criteria specified
  519. return count();
  520. }
  521. if (logger.isDebugEnabled()) {
  522. logger.debug(String.format("count called with Criteria=%s and joins=%s ", criteria,
  523. joins!=null ? Arrays.asList(joins) : "no joins"));
  524. }
  525. final Group group = criteria != null ? Group.and(criteria) : null;
  526. final StringBuilder sbquery = new StringBuilder("SELECT count("
  527. + (this.distinct ? "DISTINCT " : "") + "o ) ");
  528. sbquery.append(CriteriaUtils.constructFrom(type, joins));
  529. sbquery.append(CriteriaUtils.constructJoins(joins));
  530. sbquery.append(" ");
  531. sbquery.append(CriteriaUtils.constuctWhereClause(group));
  532. return executeCountQuery(group, sbquery.toString(), criteria);
  533. }
  534. private int executeCountQuery(final Group group, final String squery,
  535. final Criterion... criteria) {
  536. try {
  537. Query query = this.getEntityManager()
  538. .createQuery(squery.toString());
  539. if (criteria != null) {
  540. GenericDaoUtils.addGroupParams(query, group, null);
  541. }
  542. return ((Long) query.getResultList().get(0)).intValue();
  543. } catch (Exception ex) {
  544. throw new RuntimeException("Unable to run query : " + squery, ex);
  545. }
  546. }
  547. public void clear() {
  548. getEntityManager().clear();
  549. }
  550. public void flush() {
  551. getEntityManager().flush();
  552. }
  553. public String getIdPropertyName() {
  554. if (idPropertyName==null) {
  555. idPropertyName = GenericDaoUtils.searchFieldsForPK(this.type);
  556. if (idPropertyName==null) {
  557. logger.debug("Unable to find @Id in fields looking in getter methods");
  558. idPropertyName = GenericDaoUtils.searchMethodsForPK(this.type);
  559. }
  560. if (idPropertyName==null) {
  561. logger.debug("Unable to find @Id using default of id");
  562. idPropertyName="id";
  563. }
  564. }
  565. return idPropertyName;
  566. }
  567. @SuppressWarnings("unchecked")
  568. public List<Object[]> find(Select[] selects, Join[] joins,
  569. OrderBy[] orderBy, int startPosition, int maxResults,
  570. Criterion... criteria) {
  571. final Group group = criteria != null ? Group.and(criteria)
  572. : new Group();
  573. final String sQuery = CriteriaUtils.createQuery(this.type, selects, this.newSelectStatement, this.distinct,
  574. orderBy, joins, group);
  575. return (List<Object[]>)executeQueryWithJPA(criteria, startPosition, maxResults, group,
  576. sQuery);
  577. }
  578. @SuppressWarnings("unchecked")
  579. private List executeQueryWithJPA(final Criterion[] criteria,
  580. final int startPosition, final int maxResult, final Group group,
  581. final String sQuery) {
  582. try {
  583. Query query = getEntityManager().createQuery(sQuery);
  584. if (criteria != null) {
  585. GenericDaoUtils.addGroupParams(query, group, null);
  586. }
  587. if (startPosition != -1 && maxResult != -1) {
  588. query.setFirstResult(startPosition);
  589. query.setMaxResults(maxResult);
  590. }
  591. prepareQueryHintsIfNeeded(query);
  592. return query.getResultList();
  593. } catch (Exception ex) {
  594. logger.debug("failed to run a query", ex);
  595. throw new RuntimeException("Unable to run query : " + sQuery, ex);
  596. }
  597. }
  598. private final void prepareQueryHintsIfNeeded(Query query) {
  599. if (queryHints!=null && queryHints.size()>0) {
  600. for (QueryHint<?> qh : queryHints) {
  601. query.setHint(qh.getName(), qh.getValue());
  602. }
  603. }
  604. }
  605. public <Z> Z getReference(Class<Z> entityClass, Object primaryKey) {
  606. return this.getEntityManager().getReference(entityClass, primaryKey);
  607. }
  608. public List<T> find(Map<String, Object> propertyValues, int startRecord,
  609. int numRecords) {
  610. return doFind(this.type, (Select[])null, true, (OrderBy[]) null,
  611. new Criterion[]{Group.and(propertyValues)}, (Join[]) null, startRecord, numRecords);
  612. }
  613. }