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
019package org.apache.oozie.servlet;
020
021import java.io.IOException;
022import java.util.Arrays;
023
024import javax.servlet.ServletException;
025import javax.servlet.http.HttpServletRequest;
026import javax.servlet.http.HttpServletResponse;
027
028import org.apache.hadoop.conf.Configuration;
029import org.apache.oozie.BaseEngineException;
030import org.apache.oozie.ErrorCode;
031import org.apache.oozie.client.OozieClient;
032import org.apache.oozie.client.XOozieClient;
033import org.apache.oozie.client.rest.JsonBean;
034import org.apache.oozie.client.rest.JsonTags;
035import org.apache.oozie.client.rest.RestConstants;
036import org.apache.oozie.service.AuthorizationException;
037import org.apache.oozie.service.AuthorizationService;
038import org.apache.oozie.service.Services;
039import org.apache.oozie.service.XLogService;
040import org.apache.oozie.util.ConfigUtils;
041import org.apache.oozie.util.JobUtils;
042import org.apache.oozie.util.XConfiguration;
043import org.apache.oozie.util.XLog;
044import org.json.simple.JSONArray;
045import org.json.simple.JSONObject;
046
047public abstract class BaseJobServlet extends JsonRestServlet {
048
049    private static final ResourceInfo RESOURCES_INFO[] = new ResourceInfo[1];
050
051    final static String NOT_SUPPORTED_MESSAGE = "Not supported in this version";
052
053    static {
054        RESOURCES_INFO[0] = new ResourceInfo("*", Arrays.asList("PUT", "GET"), Arrays.asList(new ParameterInfo(
055                RestConstants.ACTION_PARAM, String.class, true, Arrays.asList("PUT")), new ParameterInfo(
056                RestConstants.JOB_SHOW_PARAM, String.class, false, Arrays.asList("GET")), new ParameterInfo(
057                        RestConstants.ORDER_PARAM, String.class, false, Arrays.asList("GET"))));
058    }
059
060    public BaseJobServlet(String instrumentationName) {
061        super(instrumentationName, RESOURCES_INFO);
062    }
063
064    /**
065     * Perform various job related actions - start, suspend, resume, kill, etc.
066     */
067    @Override
068    protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
069        String jobId = getResourceName(request);
070        request.setAttribute(AUDIT_PARAM, jobId);
071        request.setAttribute(AUDIT_OPERATION, request.getParameter(RestConstants.ACTION_PARAM));
072        try {
073            AuthorizationService auth = Services.get().get(AuthorizationService.class);
074            auth.authorizeForJob(getUser(request), jobId, true);
075        }
076        catch (AuthorizationException ex) {
077            throw new XServletException(HttpServletResponse.SC_UNAUTHORIZED, ex);
078        }
079
080        String action = request.getParameter(RestConstants.ACTION_PARAM);
081        if (action.equals(RestConstants.JOB_ACTION_START)) {
082            stopCron();
083            startJob(request, response);
084            startCron();
085            response.setStatus(HttpServletResponse.SC_OK);
086        }
087        else if (action.equals(RestConstants.JOB_ACTION_RESUME)) {
088            stopCron();
089            resumeJob(request, response);
090            startCron();
091            response.setStatus(HttpServletResponse.SC_OK);
092        }
093        else if (action.equals(RestConstants.JOB_ACTION_SUSPEND)) {
094            stopCron();
095            suspendJob(request, response);
096            startCron();
097            response.setStatus(HttpServletResponse.SC_OK);
098        }
099        else if (action.equals(RestConstants.JOB_ACTION_KILL)) {
100            stopCron();
101            JSONObject json =  killJob(request, response);
102            startCron();
103            if (json != null) {
104                sendJsonResponse(response, HttpServletResponse.SC_OK, json);
105            }
106            else {
107                response.setStatus(HttpServletResponse.SC_OK);
108            }
109        }
110        else if (action.equals(RestConstants.JOB_ACTION_CHANGE)) {
111            stopCron();
112            changeJob(request, response);
113            startCron();
114            response.setStatus(HttpServletResponse.SC_OK);
115        }
116        else if (action.equals(RestConstants.JOB_ACTION_IGNORE)) {
117            stopCron();
118            JSONObject json = ignoreJob(request, response);
119            startCron();
120            if (json != null) {
121                sendJsonResponse(response, HttpServletResponse.SC_OK, json);
122            }
123            else {
124            response.setStatus(HttpServletResponse.SC_OK);
125            }
126        }
127        else if (action.equals(RestConstants.JOB_ACTION_RERUN)) {
128            validateContentType(request, RestConstants.XML_CONTENT_TYPE);
129            Configuration conf = new XConfiguration(request.getInputStream());
130            stopCron();
131            String requestUser = getUser(request);
132            if (!requestUser.equals(UNDEF)) {
133                conf.set(OozieClient.USER_NAME, requestUser);
134            }
135            if (conf.get(OozieClient.APP_PATH) != null) {
136                BaseJobServlet.checkAuthorizationForApp(conf);
137                JobUtils.normalizeAppPath(conf.get(OozieClient.USER_NAME), conf.get(OozieClient.GROUP_NAME), conf);
138            }
139            reRunJob(request, response, conf);
140            startCron();
141            response.setStatus(HttpServletResponse.SC_OK);
142        }
143        else if (action.equals(RestConstants.JOB_COORD_ACTION_RERUN)) {
144            validateContentType(request, RestConstants.XML_CONTENT_TYPE);
145            stopCron();
146            JSONObject json = reRunJob(request, response, null);
147            startCron();
148            if (json != null) {
149                sendJsonResponse(response, HttpServletResponse.SC_OK, json);
150            }
151            else {
152                response.setStatus(HttpServletResponse.SC_OK);
153            }
154        }
155        else if (action.equals(RestConstants.JOB_BUNDLE_ACTION_RERUN)) {
156            validateContentType(request, RestConstants.XML_CONTENT_TYPE);
157            stopCron();
158            JSONObject json = reRunJob(request, response, null);
159            startCron();
160            if (json != null) {
161                sendJsonResponse(response, HttpServletResponse.SC_OK, json);
162            }
163            else {
164                response.setStatus(HttpServletResponse.SC_OK);
165            }
166        }
167        else if (action.equals(RestConstants.JOB_COORD_UPDATE)) {
168            validateContentType(request, RestConstants.XML_CONTENT_TYPE);
169            Configuration conf = new XConfiguration(request.getInputStream());
170            stopCron();
171            String requestUser = getUser(request);
172            if (!requestUser.equals(UNDEF)) {
173                conf.set(OozieClient.USER_NAME, requestUser);
174            }
175            if (conf.get(OozieClient.COORDINATOR_APP_PATH) != null) {
176                //If coord is submitted from bundle, user may want to update individual coord job with bundle properties
177                //If COORDINATOR_APP_PATH is set, we should check only COORDINATOR_APP_PATH path permission
178                String bundlePath = conf.get(OozieClient.BUNDLE_APP_PATH);
179                if (bundlePath != null) {
180                    conf.unset(OozieClient.BUNDLE_APP_PATH);
181                }
182                BaseJobServlet.checkAuthorizationForApp(conf);
183                JobUtils.normalizeAppPath(conf.get(OozieClient.USER_NAME), conf.get(OozieClient.GROUP_NAME), conf);
184                if (bundlePath != null) {
185                    conf.set(OozieClient.BUNDLE_APP_PATH, bundlePath);
186                }
187            }
188            JSONObject json = updateJob(request, response, conf);
189            startCron();
190            sendJsonResponse(response, HttpServletResponse.SC_OK, json);
191        }
192        else if (action.equals(RestConstants.SLA_ENABLE_ALERT)) {
193            validateContentType(request, RestConstants.XML_CONTENT_TYPE);
194            stopCron();
195            slaEnableAlert(request, response);
196            startCron();
197            response.setStatus(HttpServletResponse.SC_OK);
198        }
199        else if (action.equals(RestConstants.SLA_DISABLE_ALERT)) {
200            validateContentType(request, RestConstants.XML_CONTENT_TYPE);
201            stopCron();
202            slaDisableAlert(request, response);
203            startCron();
204            response.setStatus(HttpServletResponse.SC_OK);
205        }
206        else if (action.equals(RestConstants.SLA_CHANGE)) {
207            validateContentType(request, RestConstants.XML_CONTENT_TYPE);
208            stopCron();
209            slaChange(request, response);
210            startCron();
211            response.setStatus(HttpServletResponse.SC_OK);
212        }
213        else {
214            throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, ErrorCode.E0303,
215                    RestConstants.ACTION_PARAM, action);
216        }
217    }
218
219    abstract JSONObject ignoreJob(HttpServletRequest request, HttpServletResponse response) throws XServletException,
220            IOException;
221
222    /**
223     * Validate the configuration user/group. <p>
224     *
225     * @param conf configuration.
226     * @throws XServletException thrown if the configuration does not have a property {@link
227     * org.apache.oozie.client.OozieClient#USER_NAME}.
228     */
229    static void checkAuthorizationForApp(Configuration conf) throws XServletException {
230        String user = conf.get(OozieClient.USER_NAME);
231        String acl = ConfigUtils.getWithDeprecatedCheck(conf, OozieClient.GROUP_NAME, OozieClient.JOB_ACL, null);
232        try {
233            if (user == null) {
234                throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, ErrorCode.E0401, OozieClient.USER_NAME);
235            }
236            AuthorizationService auth = Services.get().get(AuthorizationService.class);
237
238            if (acl != null){
239                 conf.set(OozieClient.GROUP_NAME, acl);
240            }
241            else if (acl == null && auth.useDefaultGroupAsAcl()) {
242                acl = auth.getDefaultGroup(user);
243                conf.set(OozieClient.GROUP_NAME, acl);
244            }
245            XLog.Info.get().setParameter(XLogService.GROUP, acl);
246            String wfPath = conf.get(OozieClient.APP_PATH);
247            String coordPath = conf.get(OozieClient.COORDINATOR_APP_PATH);
248            String bundlePath = conf.get(OozieClient.BUNDLE_APP_PATH);
249
250            if (wfPath == null && coordPath == null && bundlePath == null) {
251                String[] libPaths = conf.getStrings(XOozieClient.LIBPATH);
252                if (libPaths != null && libPaths.length > 0 && libPaths[0].trim().length() > 0) {
253                    conf.set(OozieClient.APP_PATH, libPaths[0].trim());
254                    wfPath = libPaths[0].trim();
255                }
256                else {
257                    throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, ErrorCode.E0405);
258                }
259            }
260            ServletUtilities.ValidateAppPath(wfPath, coordPath, bundlePath);
261
262            if (wfPath != null) {
263                auth.authorizeForApp(user, acl, wfPath, "workflow.xml", conf);
264            }
265            else if (coordPath != null){
266                auth.authorizeForApp(user, acl, coordPath, "coordinator.xml", conf);
267            }
268            else if (bundlePath != null){
269                auth.authorizeForApp(user, acl, bundlePath, "bundle.xml", conf);
270            }
271        }
272        catch (AuthorizationException ex) {
273            XLog.getLog(BaseJobServlet.class).info("AuthorizationException ", ex);
274            throw new XServletException(HttpServletResponse.SC_UNAUTHORIZED, ex);
275        }
276    }
277
278    /**
279     * Return information about jobs.
280     */
281    @Override
282    @SuppressWarnings("unchecked")
283    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
284        String jobId = getResourceName(request);
285        String show = request.getParameter(RestConstants.JOB_SHOW_PARAM);
286        String timeZoneId = request.getParameter(RestConstants.TIME_ZONE_PARAM) == null
287                ? "GMT" : request.getParameter(RestConstants.TIME_ZONE_PARAM);
288
289        try {
290            AuthorizationService auth = Services.get().get(AuthorizationService.class);
291            auth.authorizeForJob(getUser(request), jobId, false);
292        }
293        catch (AuthorizationException ex) {
294            throw new XServletException(HttpServletResponse.SC_UNAUTHORIZED, ex);
295        }
296
297        if (show == null || show.equals(RestConstants.JOB_SHOW_INFO)) {
298            stopCron();
299            JsonBean job = null;
300            try {
301                job = getJob(request, response);
302            }
303            catch (BaseEngineException e) {
304                // TODO Auto-generated catch block
305                // e.printStackTrace();
306
307                throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, e);
308            }
309            startCron();
310            sendJsonResponse(response, HttpServletResponse.SC_OK, job, timeZoneId);
311        }
312        else if (show.equals(RestConstants.ALL_WORKFLOWS_FOR_COORD_ACTION)) {
313            stopCron();
314            JSONObject json = getJobsByParentId(request, response);
315            startCron();
316            sendJsonResponse(response, HttpServletResponse.SC_OK, json);
317        }
318        else if (show.equals(RestConstants.JOB_SHOW_JMS_TOPIC)) {
319            stopCron();
320            String jmsTopicName = getJMSTopicName(request, response);
321            JSONObject json = new JSONObject();
322            json.put(JsonTags.JMS_TOPIC_NAME, jmsTopicName);
323            startCron();
324            sendJsonResponse(response, HttpServletResponse.SC_OK, json);
325        }
326
327        else if (show.equals(RestConstants.JOB_SHOW_LOG)) {
328            response.setContentType(TEXT_UTF8);
329            streamJobLog(request, response);
330        }
331        else if (show.equals(RestConstants.JOB_SHOW_ERROR_LOG)) {
332            response.setContentType(TEXT_UTF8);
333            streamJobErrorLog(request, response);
334        }
335        else if (show.equals(RestConstants.JOB_SHOW_AUDIT_LOG)) {
336            response.setContentType(TEXT_UTF8);
337            streamJobAuditLog(request, response);
338        }
339
340        else if (show.equals(RestConstants.JOB_SHOW_DEFINITION)) {
341            stopCron();
342            response.setContentType(XML_UTF8);
343            String wfDefinition = getJobDefinition(request, response);
344            startCron();
345            response.setStatus(HttpServletResponse.SC_OK);
346            response.getWriter().write(wfDefinition);
347        }
348        else if (show.equals(RestConstants.JOB_SHOW_GRAPH)) {
349            stopCron();
350            streamJobGraph(request, response);
351            startCron(); // -- should happen before you stream anything in response?
352        } else if (show.equals(RestConstants.JOB_SHOW_STATUS)) {
353            stopCron();
354            String status = getJobStatus(request, response);
355            JSONObject json = new JSONObject();
356            json.put(JsonTags.STATUS, status);
357            startCron();
358            sendJsonResponse(response, HttpServletResponse.SC_OK, json);
359        } else if (show.equals(RestConstants.JOB_SHOW_ACTION_RETRIES_PARAM)) {
360            stopCron();
361            JSONArray retries = getActionRetries(request, response);
362            JSONObject json = new JSONObject();
363            json.put(JsonTags.WORKFLOW_ACTION_RETRIES, retries);
364            startCron();
365            sendJsonResponse(response, HttpServletResponse.SC_OK, json);
366        }
367        else if (show.equals(RestConstants.COORD_ACTION_MISSING_DEPENDENCIES)) {
368            stopCron();
369            JSONObject json = getCoordActionMissingDependencies(request, response);
370            startCron();
371            sendJsonResponse(response, HttpServletResponse.SC_OK, json);
372        }
373        else if (show.equals(RestConstants.JOB_SHOW_WF_ACTIONS_IN_COORD)) {
374            stopCron();
375            JSONObject json = getWfActionByJobIdAndName(request, response);
376            startCron();
377            sendJsonResponse(response, HttpServletResponse.SC_OK, json);
378        }
379        else {
380            throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, ErrorCode.E0303,
381                    RestConstants.JOB_SHOW_PARAM, show);
382        }
383    }
384
385    /**
386     * abstract method to start a job, either workflow or coordinator
387     *
388     * @param request the request
389     * @param response the response
390     * @throws XServletException in case of any servlet error
391     * @throws IOException in case of any I/O error
392     */
393    abstract void startJob(HttpServletRequest request, HttpServletResponse response) throws XServletException,
394            IOException;
395
396    /**
397     * abstract method to resume a job, either workflow or coordinator
398     *
399     * @param request the request
400     * @param response the response
401     * @throws XServletException in case of any servlet error
402     * @throws IOException in case of any I/O error
403     */
404    abstract void resumeJob(HttpServletRequest request, HttpServletResponse response) throws XServletException,
405            IOException;
406
407    /**
408     * abstract method to suspend a job, either workflow or coordinator
409     *
410     * @param request the request
411     * @param response the response
412     * @throws XServletException in case of any servlet error
413     * @throws IOException in case of any I/O error
414     */
415    abstract void suspendJob(HttpServletRequest request, HttpServletResponse response) throws XServletException,
416            IOException;
417
418    /**
419     * abstract method to kill a job, either workflow or coordinator
420     *
421     * @param request the request
422     * @param response the response
423     * @throws XServletException in case of any servlet error
424     * @throws IOException in case of any I/O error
425     */
426    abstract JSONObject killJob(HttpServletRequest request, HttpServletResponse response) throws XServletException,
427            IOException;
428
429    /**
430     * abstract method to change a coordinator job
431     *
432     * @param request the request
433     * @param response the response
434     * @throws XServletException in case of any servlet error
435     * @throws IOException in case of any I/O error
436    */
437    abstract void changeJob(HttpServletRequest request, HttpServletResponse response) throws XServletException,
438            IOException;
439
440    /**
441     * abstract method to re-run a job, either workflow or coordinator
442     *
443     * @param request the request
444     * @param response the response
445     * @param conf the configuration to use
446     * @throws XServletException in case of any servlet error
447     * @throws IOException in case of any I/O error
448     */
449    abstract JSONObject reRunJob(HttpServletRequest request, HttpServletResponse response, Configuration conf)
450            throws XServletException, IOException;
451
452    /**
453     * abstract method to get a job, either workflow or coordinator, in JsonBean representation
454     * @param request the request
455     * @param response the response
456     * @return JsonBean representation of a job, either workflow or coordinator
457     * @throws XServletException in case of any servlet error
458     * @throws IOException in case of any I/O error
459     * @throws BaseEngineException thrown if the job could not be retrieved
460     */
461    abstract JsonBean getJob(HttpServletRequest request, HttpServletResponse response) throws XServletException,
462            IOException, BaseEngineException;
463
464    /**
465     * abstract method to get definition of a job, either workflow or coordinator
466     *
467     * @param request the request
468     * @param response the response
469     * @return job, either workflow or coordinator, definition in string format
470     * @throws XServletException in case of any servlet error
471     * @throws IOException in case of any I/O error
472     */
473    abstract String getJobDefinition(HttpServletRequest request, HttpServletResponse response)
474            throws XServletException, IOException;
475
476    /**
477     * abstract method to get and stream log information of job, either workflow or coordinator
478     *
479     * @param request the request
480     * @param response the response
481     * @throws XServletException in case of any servlet error
482     * @throws IOException in case of any I/O error
483     */
484    abstract void streamJobLog(HttpServletRequest request, HttpServletResponse response) throws XServletException,
485            IOException;
486
487    /**
488     * abstract method to get and stream error log information of job, either workflow, coordinator or bundle
489     *
490     * @param request the request
491     * @param response the response
492     * @throws XServletException in case of any servlet error
493     * @throws IOException in case of any I/O error
494     */
495    abstract void streamJobErrorLog(HttpServletRequest request, HttpServletResponse response) throws XServletException,
496    IOException;
497
498
499    abstract void streamJobAuditLog(HttpServletRequest request, HttpServletResponse response) throws XServletException,
500            IOException;
501
502    /**
503     * abstract method to create and stream image for runtime DAG -- workflow only
504     *
505     * @param request the request
506     * @param response the response
507     * @throws XServletException in case of any servlet error
508     * @throws IOException in case of any I/O error
509     */
510    abstract void streamJobGraph(HttpServletRequest request, HttpServletResponse response)
511            throws XServletException, IOException;
512
513    /**
514     * abstract method to get JMS topic name for a job
515     * @param request the request
516     * @param response the response
517     * @throws XServletException in case of any servlet error
518     * @throws IOException in case of any I/O error
519     */
520    abstract String getJMSTopicName(HttpServletRequest request, HttpServletResponse response)
521            throws XServletException, IOException;
522
523    /**
524     * abstract method to get workflow job ids from the parent id
525     * i.e. coordinator action
526     * @param request the request
527     * @param response the response
528     * @return comma-separated list of workflow job ids
529     * @throws XServletException in case of any servlet error
530     * @throws IOException in case of any I/O error
531     */
532    abstract JSONObject getJobsByParentId(HttpServletRequest request, HttpServletResponse response)
533            throws XServletException, IOException;
534
535    /**
536     * Abstract method to Update coord job.
537     *
538     * @param request the request
539     * @param response the response
540     * @param conf the Configuration
541     * @return the JSON object
542     * @throws XServletException the x servlet exception
543     * @throws IOException Signals that an I/O exception has occurred.
544     */
545    abstract JSONObject updateJob(HttpServletRequest request, HttpServletResponse response, Configuration conf)
546            throws XServletException, IOException;
547
548    /**
549     * Abstract method to get status for a job
550     *
551     * @param request the request
552     * @param response the response
553     * @return the JSON object
554     * @throws XServletException the x servlet exception
555     * @throws IOException Signals that an I/O exception has occurred.
556     */
557    abstract String getJobStatus(HttpServletRequest request, HttpServletResponse response)
558            throws XServletException, IOException;
559
560    /**
561     * Abstract method to enable SLA alert.
562     *
563     * @param request the request
564     * @param response the response
565     * @throws XServletException the x servlet exception
566     * @throws IOException Signals that an I/O exception has occurred.
567     */
568    abstract void slaEnableAlert(HttpServletRequest request, HttpServletResponse response) throws XServletException,
569            IOException;
570
571    /**
572     * Abstract method to disable SLA alert.
573     *
574     * @param request the request
575     * @param response the response
576     * @throws XServletException the x servlet exception
577     * @throws IOException Signals that an I/O exception has occurred.
578     */
579    abstract void slaDisableAlert(HttpServletRequest request, HttpServletResponse response) throws XServletException,
580            IOException;
581
582    /**
583     * Abstract method to change SLA definition.
584     *
585     * @param request the request
586     * @param response the response
587     * @throws XServletException the x servlet exception
588     * @throws IOException Signals that an I/O exception has occurred.
589     */
590    abstract void slaChange(HttpServletRequest request, HttpServletResponse response) throws XServletException,
591            IOException;
592
593    /**
594     * Gets the action retries.
595     *
596     * @param request the request
597     * @param response the response
598     * @return the action retries
599     * @throws XServletException the x servlet exception
600     * @throws IOException Signals that an I/O exception has occurred.
601     */
602    abstract JSONArray getActionRetries(HttpServletRequest request, HttpServletResponse response)
603            throws XServletException, IOException;
604
605    /**
606     * Abstract method to get the coord action missing dependencies.
607     *
608     * @param request the request
609     * @param response the response
610     * @return the coord input dependencies
611     * @throws XServletException the x servlet exception
612     * @throws IOException Signals that an I/O exception has occurred.
613     */
614    abstract JSONObject getCoordActionMissingDependencies(HttpServletRequest request, HttpServletResponse response)
615            throws XServletException, IOException;
616
617    /**
618     * get wf actions by name in coordinator job
619     *
620     * @param request the request
621     * @param response the response
622     * @return JSONObject the JSON object
623     * @throws XServletException the x servlet exception
624     * @throws IOException Signals that an I/O exception has occurred.
625     */
626    protected JSONObject getWfActionByJobIdAndName(HttpServletRequest request, HttpServletResponse response)
627            throws XServletException, IOException {
628        throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, ErrorCode.E0302, NOT_SUPPORTED_MESSAGE);
629    }
630}