001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     * 
010     *      http://www.apache.org/licenses/LICENSE-2.0
011     * 
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    package org.apache.oozie;
019    
020    import java.io.DataInput;
021    import java.io.DataOutput;
022    import java.io.IOException;
023    import java.sql.Timestamp;
024    import java.util.Date;
025    
026    import javax.persistence.Basic;
027    import javax.persistence.Column;
028    import javax.persistence.Entity;
029    import javax.persistence.Lob;
030    import javax.persistence.NamedQueries;
031    import javax.persistence.NamedQuery;
032    
033    import org.apache.hadoop.io.Writable;
034    import org.apache.oozie.client.CoordinatorJob;
035    import org.apache.oozie.client.rest.JsonCoordinatorJob;
036    import org.apache.oozie.util.DateUtils;
037    import org.apache.oozie.util.WritableUtils;
038    import org.apache.openjpa.persistence.jdbc.Index;
039    
040    @Entity
041    @NamedQueries( {
042            @NamedQuery(name = "UPDATE_COORD_JOB", query = "update CoordinatorJobBean w set w.appName = :appName, w.appPath = :appPath, w.concurrency = :concurrency, w.conf = :conf, w.externalId = :externalId, w.frequency = :frequency, w.lastActionNumber = :lastActionNumber, w.timeOut = :timeOut, w.timeZone = :timeZone, w.authToken = :authToken, w.createdTimestamp = :createdTime, w.endTimestamp = :endTime, w.execution = :execution, w.jobXml = :jobXml, w.lastActionTimestamp = :lastAction, w.lastModifiedTimestamp = :lastModifiedTime, w.nextMaterializedTimestamp = :nextMaterializedTime, w.origJobXml = :origJobXml, w.slaXml=:slaXml, w.startTimestamp = :startTime, w.status = :status, w.timeUnitStr = :timeUnit where w.id = :id"),
043    
044            @NamedQuery(name = "UPDATE_COORD_JOB_STATUS", query = "update CoordinatorJobBean w set w.status = :status, w.lastModifiedTimestamp = :lastModifiedTime where w.id = :id"),
045    
046            @NamedQuery(name = "DELETE_COORD_JOB", query = "delete from CoordinatorJobBean w where w.id = :id"),
047    
048            @NamedQuery(name = "GET_COORD_JOBS", query = "select OBJECT(w) from CoordinatorJobBean w"),
049    
050            @NamedQuery(name = "GET_COORD_JOB", query = "select OBJECT(w) from CoordinatorJobBean w where w.id = :id"),
051    
052            @NamedQuery(name = "GET_COORD_JOBS_PENDING", query = "select OBJECT(w) from CoordinatorJobBean w where w.pending = 1 order by w.lastModifiedTimestamp"),
053    
054            @NamedQuery(name = "GET_COORD_JOBS_COUNT", query = "select count(w) from CoordinatorJobBean w"),
055    
056            @NamedQuery(name = "GET_COORD_JOBS_COLUMNS", query = "select w.id, w.appName, w.status, w.user, w.group, w.startTimestamp, w.endTimestamp, w.appPath, w.concurrency, w.frequency, w.lastActionTimestamp, w.nextMaterializedTimestamp, w.createdTimestamp, w.timeUnitStr, w.timeZone, w.timeOut from CoordinatorJobBean w order by w.createdTimestamp desc"),
057    
058            @NamedQuery(name = "GET_COORD_JOBS_OLDER_THAN", query = "select OBJECT(w) from CoordinatorJobBean w where w.startTimestamp <= :matTime AND (w.status = 'PREP' OR w.status = 'RUNNING') AND (w.nextMaterializedTimestamp < :matTime OR w.nextMaterializedTimestamp IS NULL) AND (w.nextMaterializedTimestamp IS NULL OR (w.endTimestamp > w.nextMaterializedTimestamp AND (w.pauseTimestamp IS NULL OR w.pauseTimestamp > w.nextMaterializedTimestamp))) order by w.lastModifiedTimestamp"),
059    
060            @NamedQuery(name = "GET_COORD_JOBS_OLDER_THAN_STATUS", query = "select OBJECT(w) from CoordinatorJobBean w where w.status = :status AND w.lastModifiedTimestamp <= :lastModTime order by w.lastModifiedTimestamp"),
061    
062            @NamedQuery(name = "GET_COMPLETED_COORD_JOBS_OLDER_THAN_STATUS", query = "select OBJECT(w) from CoordinatorJobBean w where ( w.status = 'SUCCEEDED' OR w.status = 'FAILED' or w.status = 'KILLED') AND w.lastModifiedTimestamp <= :lastModTime order by w.lastModifiedTimestamp"),
063    
064            @NamedQuery(name = "GET_COORD_JOBS_UNPAUSED", query = "select OBJECT(w) from CoordinatorJobBean w where w.status = 'RUNNING' OR w.status = 'RUNNINGWITHERROR' OR w.status = 'PREP' order by w.lastModifiedTimestamp"),
065    
066            @NamedQuery(name = "GET_COORD_JOBS_PAUSED", query = "select OBJECT(w) from CoordinatorJobBean w where w.status = 'PAUSED' OR w.status = 'PAUSEDWITHERROR' OR w.status = 'PREPPAUSED' order by w.lastModifiedTimestamp"),
067    
068            @NamedQuery(name = "GET_COORD_JOBS_FOR_BUNDLE", query = "select OBJECT(w) from CoordinatorJobBean w where w.bundleId = :bundleId order by w.lastModifiedTimestamp") })
069    public class CoordinatorJobBean extends JsonCoordinatorJob implements Writable {
070    
071        @Basic
072        @Index
073        @Column(name = "status")
074        private String status = CoordinatorJob.Status.PREP.toString();
075    
076        @Basic
077        @Column(name = "auth_token")
078        @Lob
079        private String authToken = null;
080    
081        @Basic
082        @Column(name = "start_time")
083        private java.sql.Timestamp startTimestamp = null;
084    
085        @Basic
086        @Column(name = "end_time")
087        private java.sql.Timestamp endTimestamp = null;
088    
089        @Basic
090        @Column(name = "pause_time")
091        private java.sql.Timestamp pauseTimestamp = null;
092    
093        @Basic
094        @Index
095        @Column(name = "created_time")
096        private java.sql.Timestamp createdTimestamp = null;
097    
098        @Basic
099        @Column(name = "time_unit")
100        private String timeUnitStr = CoordinatorJob.Timeunit.NONE.toString();
101    
102        @Basic
103        @Column(name = "execution")
104        private String execution = CoordinatorJob.Execution.FIFO.toString();
105    
106        @Basic
107        @Column(name = "last_action")
108        private java.sql.Timestamp lastActionTimestamp = null;
109    
110        @Basic
111        @Index
112        @Column(name = "next_matd_time")
113        private java.sql.Timestamp nextMaterializedTimestamp = null;
114    
115        @Basic
116        @Index
117        @Column(name = "last_modified_time")
118        private java.sql.Timestamp lastModifiedTimestamp = null;
119    
120        @Basic
121        @Index
122        @Column(name = "suspended_time")
123        private java.sql.Timestamp suspendedTimestamp = null;
124    
125        @Column(name = "job_xml")
126        @Lob
127        private String jobXml = null;
128    
129        @Column(name = "orig_job_xml")
130        @Lob
131        private String origJobXml = null;
132    
133        @Column(name = "sla_xml")
134        @Lob
135        private String slaXml = null;
136    
137        @Basic
138        @Column(name = "pending")
139        private int pending = 0;
140    
141        @Basic
142        @Column(name = "done_materialization")
143        private int doneMaterialization = 0;
144    
145        @Basic
146        @Column(name = "app_namespace")
147        private String appNamespace = null;
148    
149        /**
150         * Get start timestamp
151         *
152         * @return start timestamp
153         */
154        public java.sql.Timestamp getStartTimestamp() {
155            return startTimestamp;
156        }
157    
158        /**
159         * Set start timestamp
160         *
161         * @param startTimestamp start timestamp
162         */
163        public void setStartTimestamp(java.sql.Timestamp startTimestamp) {
164            super.setStartTime(DateUtils.toDate(startTimestamp));
165            this.startTimestamp = startTimestamp;
166        }
167    
168        /**
169         * Get end timestamp
170         *
171         * @return end timestamp
172         */
173        public java.sql.Timestamp getEndTimestamp() {
174            return endTimestamp;
175        }
176    
177        /**
178         * Set end timestamp
179         *
180         * @param endTimestamp end timestamp
181         */
182        public void setEndTimestamp(java.sql.Timestamp endTimestamp) {
183            super.setEndTime(DateUtils.toDate(endTimestamp));
184            this.endTimestamp = endTimestamp;
185        }
186    
187        /**
188         * Get next materialized timestamp
189         *
190         * @return next materialized timestamp
191         */
192        public Timestamp getNextMaterializedTimestamp() {
193            return nextMaterializedTimestamp;
194        }
195    
196        /**
197         * Set next materialized timestamp
198         *
199         * @param nextMaterializedTimestamp next materialized timestamp
200         */
201        public void setNextMaterializedTimestamp(java.sql.Timestamp nextMaterializedTimestamp) {
202            super.setNextMaterializedTime(DateUtils.toDate(nextMaterializedTimestamp));
203            this.nextMaterializedTimestamp = nextMaterializedTimestamp;
204        }
205    
206        /**
207         * Get last modified timestamp
208         *
209         * @return last modified timestamp
210         */
211        public Timestamp getLastModifiedTimestamp() {
212            return lastModifiedTimestamp;
213        }
214    
215        /**
216         * Set last modified timestamp
217         *
218         * @param lastModifiedTimestamp last modified timestamp
219         */
220        public void setLastModifiedTimestamp(java.sql.Timestamp lastModifiedTimestamp) {
221            this.lastModifiedTimestamp = lastModifiedTimestamp;
222        }
223    
224        /**
225         * Get suspended timestamp
226         *
227         * @return suspended timestamp
228         */
229        public Timestamp getSuspendedTimestamp() {
230            return suspendedTimestamp;
231        }
232    
233        /**
234         * Set suspended timestamp
235         *
236         * @param suspendedTimestamp suspended timestamp
237         */
238        public void setSuspendedTimestamp(java.sql.Timestamp suspendedTimestamp) {
239            this.suspendedTimestamp = suspendedTimestamp;
240        }
241    
242        /**
243         * Get job xml
244         *
245         * @return job xml
246         */
247        public String getJobXml() {
248            return jobXml;
249        }
250    
251        /**
252         * Set job xml
253         *
254         * @param jobXml job xml
255         */
256        public void setJobXml(String jobXml) {
257            this.jobXml = jobXml;
258        }
259    
260        /**
261         * Get original job xml
262         *
263         * @return original job xml
264         */
265        public String getOrigJobXml() {
266            return origJobXml;
267        }
268    
269        /**
270         * Set original job xml
271         *
272         * @param origJobXml
273         */
274        public void setOrigJobXml(String origJobXml) {
275            this.origJobXml = origJobXml;
276        }
277    
278        /**
279         * Get sla xml
280         *
281         * @return sla xml
282         */
283        public String getSlaXml() {
284            return slaXml;
285        }
286    
287        /**
288         * Set sla xml
289         *
290         * @param slaXml sla xml
291         */
292        public void setSlaXml(String slaXml) {
293            this.slaXml = slaXml;
294        }
295    
296        /* (non-Javadoc)
297         * @see org.apache.oozie.client.rest.JsonCoordinatorJob#setTimeUnit(org.apache.oozie.client.CoordinatorJob.Timeunit)
298         */
299        @Override
300        public void setTimeUnit(Timeunit timeUnit) {
301            super.setTimeUnit(timeUnit);
302            this.timeUnitStr = timeUnit.toString();
303        }
304    
305        /**
306         * Set last action timestamp
307         *
308         * @param lastActionTimestamp last action timestamp
309         */
310        public void setLastActionTimestamp(java.sql.Timestamp lastActionTimestamp) {
311            super.setLastActionTime(DateUtils.toDate(lastActionTimestamp));
312            this.lastActionTimestamp = lastActionTimestamp;
313        }
314    
315        /**
316         * Set auth token
317         *
318         * @param authToken auth token
319         */
320        public void setAuthToken(String authToken) {
321            this.authToken = authToken;
322        }
323    
324        /**
325         * Set pending to true
326         */
327        @Override
328        public void setPending() {
329            super.setPending();
330            this.pending = 1;
331        }
332    
333        /**
334         * Set pending to false
335         */
336        @Override
337        public void resetPending() {
338            super.resetPending();
339            this.pending = 0;
340        }
341    
342        /**
343         * Return if the action is pending.
344         *
345         * @return if the action is pending.
346         */
347        public boolean isPending() {
348            return pending == 1 ? true : false;
349        }
350    
351        /**
352         * Set doneMaterialization to true
353         */
354        public void setDoneMaterialization() {
355            this.doneMaterialization = 1;
356        }
357    
358        /**
359         * Set doneMaterialization to false
360         */
361        public void resetDoneMaterialization() {
362            this.doneMaterialization = 0;
363        }
364    
365        /**
366         * Return if the action is done with materialization
367         *
368         * @return if the action is done with materialization
369         */
370        public boolean isDoneMaterialization() {
371            return doneMaterialization == 1 ? true : false;
372        }
373    
374    
375        /**
376         * Get app namespce
377         *
378         * @return app namespce
379         */
380        public String getAppNamespace() {
381            return appNamespace;
382        }
383    
384        /**
385         * Set app namespce
386         *
387         * @param appNamespace the app namespce to set
388         */
389        public void setAppNamespace(String appNamespace) {
390            this.appNamespace = appNamespace;
391        }
392    
393        public CoordinatorJobBean() {
394        }
395    
396        /*
397         * Serialize the coordinator bean to a data output. @param dataOutput data
398         * output. @throws IOException thrown if the coordinator bean could not be
399         * serialized.
400         */
401        public void write(DataOutput dataOutput) throws IOException {
402            WritableUtils.writeStr(dataOutput, getAppPath());
403            WritableUtils.writeStr(dataOutput, getAppName());
404            WritableUtils.writeStr(dataOutput, getId());
405            WritableUtils.writeStr(dataOutput, getConf());
406            WritableUtils.writeStr(dataOutput, getStatusStr());
407            dataOutput.writeInt(getFrequency());
408            WritableUtils.writeStr(dataOutput, getTimeUnit().toString());
409            WritableUtils.writeStr(dataOutput, getTimeZone());
410            dataOutput.writeInt(getConcurrency());
411            WritableUtils.writeStr(dataOutput, getExecutionOrder().toString());
412            dataOutput.writeLong((getLastActionTime() != null) ? getLastActionTime().getTime() : -1);
413            dataOutput.writeLong((getNextMaterializedTime() != null) ? getNextMaterializedTime().getTime() : -1);
414            dataOutput.writeLong((getStartTime() != null) ? getStartTime().getTime() : -1);
415            dataOutput.writeLong((getEndTime() != null) ? getEndTime().getTime() : -1);
416            WritableUtils.writeStr(dataOutput, getUser());
417            WritableUtils.writeStr(dataOutput, getGroup());
418            WritableUtils.writeStr(dataOutput, getExternalId());
419            dataOutput.writeInt(getTimeout());
420            dataOutput.writeInt(getMatThrottling());
421            if (isPending()) {
422                dataOutput.writeInt(1);
423            } else {
424                dataOutput.writeInt(0);
425            }
426            if (isDoneMaterialization()) {
427                dataOutput.writeInt(1);
428            } else {
429                dataOutput.writeInt(0);
430            }
431            WritableUtils.writeStr(dataOutput, getAppNamespace());
432        }
433    
434        /**
435         * Deserialize a coordinator bean from a data input.
436         *
437         * @param dataInput data input.
438         * @throws IOException thrown if the workflow bean could not be deserialized.
439         */
440        public void readFields(DataInput dataInput) throws IOException {
441            setAppPath(WritableUtils.readStr(dataInput));
442            setAppName(WritableUtils.readStr(dataInput));
443            setId(WritableUtils.readStr(dataInput));
444            setConf(WritableUtils.readStr(dataInput));
445            setStatus(CoordinatorJob.Status.valueOf(WritableUtils.readStr(dataInput)));
446            setFrequency(dataInput.readInt());
447            setTimeUnit(CoordinatorJob.Timeunit.valueOf(WritableUtils.readStr(dataInput)));
448            setTimeZone(WritableUtils.readStr(dataInput));
449            setConcurrency(dataInput.readInt());
450            setExecutionOrder(Execution.valueOf(WritableUtils.readStr(dataInput)));
451    
452            long d = dataInput.readLong();
453            if (d != -1) {
454                setLastActionTime(new Date(d));
455            }
456            d = dataInput.readLong();
457            if (d != -1) {
458                setNextMaterializedTime(new Date(d));
459            }
460            d = dataInput.readLong();
461            if (d != -1) {
462                setStartTime(new Date(d));
463            }
464    
465            d = dataInput.readLong();
466            if (d != -1) {
467                setEndTime(new Date(d));
468            }
469            setUser(WritableUtils.readStr(dataInput));
470            setGroup(WritableUtils.readStr(dataInput));
471            setExternalId(WritableUtils.readStr(dataInput));
472            setTimeout(dataInput.readInt());
473            setMatThrottling(dataInput.readInt());
474    
475            d = dataInput.readInt();
476            if (d == 1) {
477                setPending();
478            }
479    
480            d = dataInput.readInt();
481            if (d == 1) {
482                setDoneMaterialization();
483            }
484    
485            setAppNamespace(WritableUtils.readStr(dataInput));
486        }
487    
488        /* (non-Javadoc)
489         * @see org.apache.oozie.client.rest.JsonCoordinatorJob#getStatus()
490         */
491        @Override
492        public Status getStatus() {
493            return Status.valueOf(this.status);
494        }
495    
496        /**
497         * Get status
498         *
499         * @return status
500         */
501        public String getStatusStr() {
502            return status;
503        }
504    
505        /* (non-Javadoc)
506         * @see org.apache.oozie.client.rest.JsonCoordinatorJob#setStatus(org.apache.oozie.client.Job.Status)
507         */
508        @Override
509        public void setStatus(Status val) {
510            super.setStatus(val);
511            this.status = val.toString();
512        }
513    
514        /**
515         * Get time unit
516         *
517         * @return time unit
518         */
519        public String getTimeUnitStr() {
520            return timeUnitStr;
521        }
522    
523        /* (non-Javadoc)
524         * @see org.apache.oozie.client.rest.JsonCoordinatorJob#getTimeUnit()
525         */
526        @Override
527        public Timeunit getTimeUnit() {
528            return Timeunit.valueOf(this.timeUnitStr);
529        }
530    
531        /**
532         * Set order
533         *
534         * @param order
535         */
536        public void setExecution(Execution order) {
537            this.execution = order.toString();
538            super.setExecutionOrder(order);
539        }
540    
541        /* (non-Javadoc)
542         * @see org.apache.oozie.client.rest.JsonCoordinatorJob#getExecutionOrder()
543         */
544        @Override
545        public Execution getExecutionOrder() {
546            return Execution.valueOf(this.execution);
547        }
548    
549        /**
550         * Get execution
551         *
552         * @return execution
553         */
554        public String getExecution() {
555            return execution;
556        }
557    
558        /* (non-Javadoc)
559         * @see org.apache.oozie.client.rest.JsonCoordinatorJob#setLastActionTime(java.util.Date)
560         */
561        @Override
562        public void setLastActionTime(Date lastAction) {
563            this.lastActionTimestamp = DateUtils.convertDateToTimestamp(lastAction);
564            super.setLastActionTime(lastAction);
565        }
566    
567        /* (non-Javadoc)
568         * @see org.apache.oozie.client.rest.JsonCoordinatorJob#getLastActionTime()
569         */
570        @Override
571        public Date getLastActionTime() {
572            return DateUtils.toDate(lastActionTimestamp);
573        }
574    
575        /**
576         * Get last action timestamp
577         *
578         * @return last action timestamp
579         */
580        public Timestamp getLastActionTimestamp() {
581            return lastActionTimestamp;
582        }
583    
584        /* (non-Javadoc)
585         * @see org.apache.oozie.client.rest.JsonCoordinatorJob#setNextMaterializedTime(java.util.Date)
586         */
587        @Override
588        public void setNextMaterializedTime(Date nextMaterializedTime) {
589            super.setNextMaterializedTime(nextMaterializedTime);
590            this.nextMaterializedTimestamp = DateUtils.convertDateToTimestamp(nextMaterializedTime);
591        }
592    
593        /* (non-Javadoc)
594         * @see org.apache.oozie.client.rest.JsonCoordinatorJob#getNextMaterializedTime()
595         */
596        @Override
597        public Date getNextMaterializedTime() {
598            return DateUtils.toDate(nextMaterializedTimestamp);
599        }
600    
601        /**
602         * Set last modified time
603         *
604         * @param lastModifiedTime last modified time
605         */
606        public void setLastModifiedTime(Date lastModifiedTime) {
607            this.lastModifiedTimestamp = DateUtils.convertDateToTimestamp(lastModifiedTime);
608        }
609    
610        /**
611         * Get last modified time
612         *
613         * @return last modified time
614         */
615        public Date getLastModifiedTime() {
616            return DateUtils.toDate(lastModifiedTimestamp);
617        }
618    
619        /**
620         * Set suspended time
621         *
622         * @param suspendedTime suspended time
623         */
624        public void setSuspendedTime(Date suspendedTime) {
625            this.suspendedTimestamp = DateUtils.convertDateToTimestamp(suspendedTime);
626        }
627    
628        /**
629         * Get suspended time
630         *
631         * @return suspended time
632         */
633        public Date getSuspendedTime() {
634            return DateUtils.toDate(suspendedTimestamp);
635        }
636    
637        /* (non-Javadoc)
638         * @see org.apache.oozie.client.rest.JsonCoordinatorJob#setStartTime(java.util.Date)
639         */
640        @Override
641        public void setStartTime(Date startTime) {
642            super.setStartTime(startTime);
643            this.startTimestamp = DateUtils.convertDateToTimestamp(startTime);
644        }
645    
646        /* (non-Javadoc)
647         * @see org.apache.oozie.client.rest.JsonCoordinatorJob#getStartTime()
648         */
649        @Override
650        public Date getStartTime() {
651            return DateUtils.toDate(startTimestamp);
652        }
653    
654        /* (non-Javadoc)
655         * @see org.apache.oozie.client.rest.JsonCoordinatorJob#setEndTime(java.util.Date)
656         */
657        @Override
658        public void setEndTime(Date endTime) {
659            super.setEndTime(endTime);
660            this.endTimestamp = DateUtils.convertDateToTimestamp(endTime);
661        }
662    
663        /* (non-Javadoc)
664         * @see org.apache.oozie.client.rest.JsonCoordinatorJob#setPauseTime(java.util.Date)
665         */
666        @Override
667        public void setPauseTime(Date pauseTime) {
668            super.setPauseTime(pauseTime);
669            this.pauseTimestamp = DateUtils.convertDateToTimestamp(pauseTime);
670        }
671    
672        /* (non-Javadoc)
673         * @see org.apache.oozie.client.rest.JsonCoordinatorJob#getEndTime()
674         */
675        @Override
676        public Date getEndTime() {
677            return DateUtils.toDate(endTimestamp);
678        }
679    
680        /* (non-Javadoc)
681         * @see org.apache.oozie.client.rest.JsonCoordinatorJob#getPauseTime()
682         */
683        @Override
684        public Date getPauseTime() {
685            return DateUtils.toDate(pauseTimestamp);
686        }
687    
688        /**
689         * Set created time
690         *
691         * @param createTime created time
692         */
693        public void setCreatedTime(Date createTime) {
694            this.createdTimestamp = DateUtils.convertDateToTimestamp(createTime);
695        }
696    
697        /**
698         * Get created time
699         *
700         * @return created time
701         */
702        public Date getCreatedTime() {
703            return DateUtils.toDate(createdTimestamp);
704        }
705    
706        /**
707         * Get created timestamp
708         *
709         * @return created timestamp
710         */
711        public Timestamp getCreatedTimestamp() {
712            return createdTimestamp;
713        }
714    
715        /**
716         * Get auth token
717         *
718         * @return auth token
719         */
720        public String getAuthToken() {
721            return this.authToken;
722        }
723    
724    }