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 */
018package org.apache.oozie.coord;
019
020import java.util.Calendar;
021import java.util.Date;
022import java.util.HashMap;
023import java.util.Iterator;
024import java.util.List;
025import java.util.Map;
026
027import org.apache.hadoop.conf.Configuration;
028import org.apache.oozie.CoordinatorActionBean;
029import org.apache.oozie.command.coord.CoordCommandUtils;
030import org.apache.oozie.service.ELService;
031import org.apache.oozie.service.Services;
032import org.apache.oozie.util.DateUtils;
033import org.apache.oozie.util.ELEvaluator;
034import org.apache.oozie.util.XmlUtils;
035import org.jdom.Element;
036
037/**
038 * This class provide different evaluators required at different stages
039 */
040public class CoordELEvaluator {
041    public static final Integer MINUTE = 1;
042    public static final Integer HOUR = 60 * MINUTE;
043
044    /**
045     * Create an evaluator to be used in resolving configuration vars and frequency constant/functions (used in Stage
046     * 1)
047     *
048     * @param conf : Configuration containing property variables
049     * @return configured ELEvaluator
050     */
051    public static ELEvaluator createELEvaluatorForGroup(Configuration conf, String group) {
052        ELEvaluator eval = Services.get().get(ELService.class).createEvaluator(group);
053        setConfigToEval(eval, conf);
054        return eval;
055    }
056
057    /**
058     * Create a new Evaluator to resolve the EL functions and variables using action creation time (Phase 2)
059     *
060     * @param event : Xml element for data-in element usually enclosed by <data-in(out)> tag
061     * @param appInst : Application Instance related information such as Action creation Time
062     * @param conf :Configuration to substitute any variables
063     * @return configured ELEvaluator
064     * @throws Exception : If there is any date-time string in wrong format, the exception is thrown
065     */
066    public static ELEvaluator createInstancesELEvaluator(Element event, SyncCoordAction appInst, Configuration conf)
067            throws Exception {
068        return createInstancesELEvaluator("coord-action-create", event, appInst, conf);
069    }
070
071    public static ELEvaluator createInstancesELEvaluator(String tag, Element event, SyncCoordAction appInst,
072                                                         Configuration conf) throws Exception {
073        ELEvaluator eval = Services.get().get(ELService.class).createEvaluator(tag);
074        setConfigToEval(eval, conf);
075        SyncCoordDataset ds = getDSObject(event);
076        CoordELFunctions.configureEvaluator(eval, ds, appInst);
077        return eval;
078    }
079
080    public static ELEvaluator createELEvaluatorForDataEcho(Configuration conf, String group,
081                                                           HashMap<String, String> dataNameList) throws Exception {
082        ELEvaluator eval = createELEvaluatorForGroup(conf, group);
083        for (Iterator<String> it = dataNameList.keySet().iterator(); it.hasNext();) {
084            String key = it.next();
085            String value = dataNameList.get(key);
086            eval.setVariable("oozie.dataname." + key, value);
087        }
088        return eval;
089    }
090
091    /**
092     * Create a new evaluator for Lazy resolve (phase 3). For example, coord_latest(n) and coord_actualTime()function
093     * should be resolved when all other data dependencies are met.
094     *
095     * @param actualTime : Action start time
096     * @param nominalTime : Action creation time
097     * @param dEvent :XML element for data-in element usually enclosed by <data-in(out)> tag
098     * @param conf :Configuration to substitute any variables
099     * @return configured ELEvaluator
100     * @throws Exception : If there is any date-time string in wrong format, the exception is thrown
101     */
102    public static ELEvaluator createLazyEvaluator(Date actualTime, Date nominalTime, Element dEvent, Configuration conf)
103            throws Exception {
104        ELEvaluator eval = Services.get().get(ELService.class).createEvaluator("coord-action-start");
105        setConfigToEval(eval, conf);
106        SyncCoordDataset ds = getDSObject(dEvent);
107        SyncCoordAction appInst = new SyncCoordAction();
108        appInst.setNominalTime(nominalTime);
109        appInst.setActualTime(actualTime);
110        CoordELFunctions.configureEvaluator(eval, ds, appInst);
111        eval.setVariable(CoordELFunctions.CONFIGURATION, conf);
112        return eval;
113    }
114
115    /**
116     * Create a SLA evaluator to be used during Materialization
117     * @param eAction
118     * @param coordAction
119     * @param conf
120     * @return
121     * @throws Exception
122     */
123    public static ELEvaluator createSLAEvaluator(Element eAction, CoordinatorActionBean coordAction, Configuration conf)
124            throws Exception {
125        ELEvaluator eval = Services.get().get(ELService.class).createEvaluator("coord-sla-create");
126        setConfigToEval(eval, conf);
127        SyncCoordAction appInst = new SyncCoordAction();// TODO:
128        appInst.setNominalTime(coordAction.getNominalTime());
129        appInst.setActualTime(coordAction.getCreatedTime());
130        appInst.setActionId(coordAction.getId());
131        appInst.setName(eAction.getAttributeValue("name"));
132        CoordELFunctions.configureEvaluator(eval, null, appInst);
133
134        Element events = eAction.getChild("output-events", eAction.getNamespace());
135        if (events != null) {
136            for (Object obj : events.getChildren("data-out", eAction.getNamespace())) {
137                Element data = (Element) obj;
138                if (data.getChild("uris", data.getNamespace()) != null) {
139                    String uris = data.getChild("uris", data.getNamespace()).getTextTrim();
140                    uris = uris.replaceAll(CoordELFunctions.INSTANCE_SEPARATOR, CoordELFunctions.DIR_SEPARATOR);
141                    eval.setVariable(".dataout." + data.getAttributeValue("name"), uris);
142                }
143                if (data.getChild(CoordCommandUtils.UNRESOLVED_INST_TAG, data.getNamespace()) != null) {
144                    eval.setVariable(".dataout." + data.getAttributeValue("name") + ".unresolved", "true");
145                }
146            }
147        }
148        return eval;
149    }
150
151    /**
152     * Create an Evaluator using conf and input/output-data (used for sla)
153     * @param conf
154     * @param group
155     * @param dataNameList
156     * @return
157     * @throws Exception
158     */
159    public static ELEvaluator createELEvaluatorForDataAndConf(Configuration conf, String group,
160            HashMap<String, String> dataNameList) throws Exception {
161        ELEvaluator eval = createELEvaluatorForDataEcho(conf, group, dataNameList);
162        setConfigToEval(eval, conf);
163        return eval;
164    }
165
166    /**
167     * Create an Evaluator to resolve dataIns and dataOuts of an application instance (used in stage 3)
168     *
169     * @param eJob : XML element for the application instance
170     * @param conf :Configuration to substitute any variables
171     * @return configured ELEvaluator
172     * @throws Exception : If there is any date-time string in wrong format, the exception is thrown
173     */
174    public static ELEvaluator createDataEvaluator(Element eJob, Configuration conf, String actionId) throws Exception {
175        ELEvaluator e = Services.get().get(ELService.class).createEvaluator("coord-action-start");
176        setConfigToEval(e, conf);
177        SyncCoordAction appInst = new SyncCoordAction();
178        String strNominalTime = eJob.getAttributeValue("action-nominal-time");
179        if (strNominalTime != null) {
180            appInst.setNominalTime(DateUtils.parseDateOozieTZ(strNominalTime));
181            appInst.setTimeZone(DateUtils.getTimeZone(eJob.getAttributeValue("timezone")));
182            appInst.setFrequency(eJob.getAttributeValue("frequency"));
183            appInst.setTimeUnit(TimeUnit.valueOf(eJob.getAttributeValue("freq_timeunit")));
184            appInst.setActionId(actionId);
185            appInst.setName(eJob.getAttributeValue("name"));
186        }
187        String strActualTime = eJob.getAttributeValue("action-actual-time");
188        if (strActualTime != null) {
189            appInst.setActualTime(DateUtils.parseDateOozieTZ(strActualTime));
190        }
191        CoordELFunctions.configureEvaluator(e, null, appInst);
192        Element events = eJob.getChild("input-events", eJob.getNamespace());
193        if (events != null) {
194            for (Element data : (List<Element>) events.getChildren("data-in", eJob.getNamespace())) {
195                if (data.getChild("uris", data.getNamespace()) != null) {
196                    String uris = data.getChild("uris", data.getNamespace()).getTextTrim();
197                    uris = uris.replaceAll(CoordELFunctions.INSTANCE_SEPARATOR, CoordELFunctions.DIR_SEPARATOR);
198                    e.setVariable(".datain." + data.getAttributeValue("name"), uris);
199                }
200                else {
201                }
202                if (data.getChild(CoordCommandUtils.UNRESOLVED_INST_TAG, data.getNamespace()) != null) {
203                    e.setVariable(".datain." + data.getAttributeValue("name") + ".unresolved", "true"); // TODO:
204                    // check
205                    // null
206                }
207            }
208        }
209        events = eJob.getChild("output-events", eJob.getNamespace());
210        if (events != null) {
211            for (Element data : (List<Element>) events.getChildren("data-out", eJob.getNamespace())) {
212                if (data.getChild("uris", data.getNamespace()) != null) {
213                    String uris = data.getChild("uris", data.getNamespace()).getTextTrim();
214                    uris = uris.replaceAll(CoordELFunctions.INSTANCE_SEPARATOR, CoordELFunctions.DIR_SEPARATOR);
215                    e.setVariable(".dataout." + data.getAttributeValue("name"), uris);
216                }
217                else {
218                }// TODO
219                if (data.getChild(CoordCommandUtils.UNRESOLVED_INST_TAG, data.getNamespace()) != null) {
220                    e.setVariable(".dataout." + data.getAttributeValue("name") + ".unresolved", "true"); // TODO:
221                    // check
222                    // null
223                }
224            }
225        }
226        return e;
227    }
228
229    /**
230     * Create a new Evaluator to resolve URI temple with time specific constant
231     *
232     * @param strDate : Date-time
233     * @return configured ELEvaluator
234     * @throws Exception If there is any date-time string in wrong format, the exception is thrown
235     */
236    public static ELEvaluator createURIELEvaluator(String strDate) throws Exception {
237        ELEvaluator eval = new ELEvaluator();
238        Calendar date = Calendar.getInstance(DateUtils.getOozieProcessingTimeZone());
239        // always???
240        date.setTime(DateUtils.parseDateOozieTZ(strDate));
241        eval.setVariable("YEAR", date.get(Calendar.YEAR));
242        eval.setVariable("MONTH", make2Digits(date.get(Calendar.MONTH) + 1));
243        eval.setVariable("DAY", make2Digits(date.get(Calendar.DAY_OF_MONTH)));
244        eval.setVariable("HOUR", make2Digits(date.get(Calendar.HOUR_OF_DAY)));
245        eval.setVariable("MINUTE", make2Digits(date.get(Calendar.MINUTE)));
246        return eval;
247    }
248
249    /**
250     * Create Dataset object using the Dataset XML information
251     *
252     * @param eData
253     * @return
254     * @throws Exception
255     */
256    private static SyncCoordDataset getDSObject(Element eData) throws Exception {
257        SyncCoordDataset ds = new SyncCoordDataset();
258        Element eDataset = eData.getChild("dataset", eData.getNamespace());
259        // System.out.println("eDATA :"+ XmlUtils.prettyPrint(eData));
260        Date initInstance = DateUtils.parseDateOozieTZ(eDataset.getAttributeValue("initial-instance"));
261        ds.setInitInstance(initInstance);
262        if (eDataset.getAttributeValue("frequency") != null) {
263            int frequency = Integer.parseInt(eDataset.getAttributeValue("frequency"));
264            ds.setFrequency(frequency);
265            ds.setType("SYNC");
266            if (eDataset.getAttributeValue("freq_timeunit") == null) {
267                throw new RuntimeException("No freq_timeunit defined in data set definition\n"
268                        + XmlUtils.prettyPrint(eDataset));
269            }
270            ds.setTimeUnit(TimeUnit.valueOf(eDataset.getAttributeValue("freq_timeunit")));
271            if (eDataset.getAttributeValue("timezone") == null) {
272                throw new RuntimeException("No timezone defined in data set definition\n"
273                        + XmlUtils.prettyPrint(eDataset));
274            }
275            ds.setTimeZone(DateUtils.getTimeZone(eDataset.getAttributeValue("timezone")));
276            if (eDataset.getAttributeValue("end_of_duration") == null) {
277                throw new RuntimeException("No end_of_duration defined in data set definition\n"
278                        + XmlUtils.prettyPrint(eDataset));
279            }
280            ds.setEndOfDuration(TimeUnit.valueOf(eDataset.getAttributeValue("end_of_duration")));
281
282            Element doneFlagElement = eDataset.getChild("done-flag", eData.getNamespace());
283            String doneFlag = CoordUtils.getDoneFlag(doneFlagElement);
284            ds.setDoneFlag(doneFlag);
285        }
286        else {
287            ds.setType("ASYNC");
288        }
289        String name = eDataset.getAttributeValue("name");
290        ds.setName(name);
291        // System.out.println(name + " VAL "+ eDataset.getChild("uri-template",
292        // eData.getNamespace()));
293        String uriTemplate = eDataset.getChild("uri-template", eData.getNamespace()).getTextTrim();
294        ds.setUriTemplate(uriTemplate);
295        // ds.setTimeUnit(TimeUnit.MINUTES);
296        return ds;
297    }
298
299    /**
300     * Set all job configurations properties into evaluator.
301     *
302     * @param eval : Evaluator to set variables
303     * @param conf : configurations to set Evaluator
304     */
305    private static void setConfigToEval(ELEvaluator eval, Configuration conf) {
306        for (Map.Entry<String, String> entry : conf) {
307            eval.setVariable(entry.getKey(), entry.getValue().trim());
308        }
309    }
310
311    /**
312     * make any one digit number to two digit string pre-appending a"0"
313     *
314     * @param num : number to make sting
315     * @return :String of length at least two digit.
316     */
317    private static String make2Digits(int num) {
318        String ret = "" + num;
319        if (num <= 9) {
320            ret = "0" + ret;
321        }
322        return ret;
323    }
324}