001    /**
002     * Copyright (c) 2011 Yahoo! Inc. All rights reserved.
003     * Licensed under the Apache License, Version 2.0 (the "License");
004     * you may not use this file except in compliance with the License.
005     * You may obtain a copy of the License at
006     *
007     *   http://www.apache.org/licenses/LICENSE-2.0
008     *
009     *  Unless required by applicable law or agreed to in writing, software
010     *  distributed under the License is distributed on an "AS IS" BASIS,
011     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012     *  See the License for the specific language governing permissions and
013     *  limitations under the License. See accompanying LICENSE file.
014     */
015    package org.apache.oozie.client.rest;
016    
017    import org.apache.oozie.client.BundleJob;
018    import org.apache.oozie.client.CoordinatorAction;
019    import org.apache.oozie.client.CoordinatorJob;
020    import org.apache.oozie.client.WorkflowAction;
021    import org.apache.oozie.client.WorkflowJob;
022    import org.json.simple.JSONArray;
023    import org.json.simple.JSONObject;
024    
025    import java.lang.reflect.InvocationHandler;
026    import java.lang.reflect.Method;
027    import java.lang.reflect.Proxy;
028    import java.util.ArrayList;
029    import java.util.Date;
030    import java.util.HashMap;
031    import java.util.List;
032    import java.util.Map;
033    
034    /**
035     * JSON to bean converter for {@link WorkflowAction}, {@link WorkflowJob}, {@link CoordinatorAction}
036     * and {@link CoordinatorJob}.
037     * <p/>
038     * It uses JDK dynamic proxy to create bean instances.
039     */
040    public class JsonToBean {
041    
042        private static class Property {
043            String label;
044            Class type;
045            boolean isList;
046    
047            public Property(String label, Class type) {
048                this(label, type, false);
049            }
050    
051            public Property(String label, Class type, boolean isList) {
052                this.label = label;
053                this.type = type;
054                this.isList = isList;
055            }
056        }
057    
058        private static final Map<String, Property> WF_JOB = new HashMap<String, Property>();
059        private static final Map<String, Property> WF_ACTION = new HashMap<String, Property>();
060        private static final Map<String, Property> COORD_JOB = new HashMap<String, Property>();
061        private static final Map<String, Property> COORD_ACTION = new HashMap<String, Property>();
062        private static final Map<String, Property> BUNDLE_JOB = new HashMap<String, Property>();
063    
064        static {
065            WF_ACTION.put("getId", new Property(JsonTags.WORKFLOW_ACTION_ID, String.class));
066            WF_ACTION.put("getName", new Property(JsonTags.WORKFLOW_ACTION_NAME, String.class));
067            WF_ACTION.put("getType", new Property(JsonTags.WORKFLOW_ACTION_TYPE, String.class));
068            WF_ACTION.put("getConf", new Property(JsonTags.WORKFLOW_ACTION_CONF, String.class));
069            WF_ACTION.put("getStatus", new Property(JsonTags.WORKFLOW_ACTION_STATUS, WorkflowAction.Status.class));
070            WF_ACTION.put("getRetries", new Property(JsonTags.WORKFLOW_ACTION_RETRIES, Integer.TYPE));
071            WF_ACTION.put("getStartTime", new Property(JsonTags.WORKFLOW_ACTION_START_TIME, Date.class));
072            WF_ACTION.put("getEndTime", new Property(JsonTags.WORKFLOW_ACTION_END_TIME, Date.class));
073            WF_ACTION.put("getTransition", new Property(JsonTags.WORKFLOW_ACTION_TRANSITION, String.class));
074            WF_ACTION.put("getData", new Property(JsonTags.WORKFLOW_ACTION_DATA, String.class));
075            WF_ACTION.put("getExternalId", new Property(JsonTags.WORKFLOW_ACTION_EXTERNAL_ID, String.class));
076            WF_ACTION.put("getExternalStatus", new Property(JsonTags.WORKFLOW_ACTION_EXTERNAL_STATUS, String.class));
077            WF_ACTION.put("getTrackerUri", new Property(JsonTags.WORKFLOW_ACTION_TRACKER_URI, String.class));
078            WF_ACTION.put("getConsoleUrl", new Property(JsonTags.WORKFLOW_ACTION_CONSOLE_URL, String.class));
079            WF_ACTION.put("getErrorCode", new Property(JsonTags.WORKFLOW_ACTION_ERROR_CODE, String.class));
080            WF_ACTION.put("getErrorMessage", new Property(JsonTags.WORKFLOW_ACTION_ERROR_MESSAGE, String.class));
081            WF_ACTION.put("toString", new Property(JsonTags.TO_STRING, String.class));
082    
083            WF_JOB.put("getAppPath", new Property(JsonTags.WORKFLOW_APP_PATH, String.class));
084            WF_JOB.put("getAppName", new Property(JsonTags.WORKFLOW_APP_NAME, String.class));
085            WF_JOB.put("getId", new Property(JsonTags.WORKFLOW_ID, String.class));
086            WF_JOB.put("getConf", new Property(JsonTags.WORKFLOW_CONF, String.class));
087            WF_JOB.put("getStatus", new Property(JsonTags.WORKFLOW_STATUS, WorkflowJob.Status.class));
088            WF_JOB.put("getLastModifiedTime", new Property(JsonTags.WORKFLOW_LAST_MOD_TIME, Date.class));
089            WF_JOB.put("getCreatedTime", new Property(JsonTags.WORKFLOW_CREATED_TIME, Date.class));
090            WF_JOB.put("getStartTime", new Property(JsonTags.WORKFLOW_CREATED_TIME, Date.class));
091            WF_JOB.put("getEndTime", new Property(JsonTags.WORKFLOW_END_TIME, Date.class));
092            WF_JOB.put("getUser", new Property(JsonTags.WORKFLOW_USER, String.class));
093            WF_JOB.put("getGroup", new Property(JsonTags.WORKFLOW_GROUP, String.class));
094            WF_JOB.put("getRun", new Property(JsonTags.WORKFLOW_RUN, Integer.TYPE));
095            WF_JOB.put("getConsoleUrl", new Property(JsonTags.WORKFLOW_CONSOLE_URL, String.class));
096            WF_JOB.put("getActions", new Property(JsonTags.WORKFLOW_ACTIONS, WorkflowAction.class, true));
097            WF_JOB.put("getParentId", new Property(JsonTags.WORKFLOW_PARENT_ID, String.class));
098            WF_JOB.put("toString", new Property(JsonTags.TO_STRING, String.class));
099    
100            COORD_ACTION.put("getId", new Property(JsonTags.COORDINATOR_ACTION_ID, String.class));
101            COORD_ACTION.put("getJobId", new Property(JsonTags.COORDINATOR_JOB_ID, String.class));
102            COORD_ACTION.put("getActionNumber", new Property(JsonTags.COORDINATOR_ACTION_NUMBER, Integer.TYPE));
103            COORD_ACTION.put("getCreatedConf", new Property(JsonTags.COORDINATOR_ACTION_CREATED_CONF, String.class));
104            COORD_ACTION.put("getCreatedTime", new Property(JsonTags.COORDINATOR_ACTION_CREATED_TIME, Date.class));
105            COORD_ACTION.put("getNominalTime", new Property(JsonTags.COORDINATOR_ACTION_NOMINAL_TIME, Date.class));
106            COORD_ACTION.put("getExternalId", new Property(JsonTags.COORDINATOR_ACTION_EXTERNALID, String.class));
107            COORD_ACTION.put("getStatus", new Property(JsonTags.COORDINATOR_ACTION_STATUS, CoordinatorAction.Status.class));
108            COORD_ACTION.put("getRunConf", new Property(JsonTags.COORDINATOR_ACTION_RUNTIME_CONF, String.class));
109            COORD_ACTION
110                    .put("getLastModifiedTime", new Property(JsonTags.COORDINATOR_ACTION_LAST_MODIFIED_TIME, Date.class));
111            COORD_ACTION
112                    .put("getMissingDependencies", new Property(JsonTags.COORDINATOR_ACTION_MISSING_DEPS, String.class));
113            COORD_ACTION.put("getExternalStatus", new Property(JsonTags.COORDINATOR_ACTION_EXTERNAL_STATUS, String.class));
114            COORD_ACTION.put("getTrackerUri", new Property(JsonTags.COORDINATOR_ACTION_TRACKER_URI, String.class));
115            COORD_ACTION.put("getConsoleUrl", new Property(JsonTags.COORDINATOR_ACTION_CONSOLE_URL, String.class));
116            COORD_ACTION.put("getErrorCode", new Property(JsonTags.COORDINATOR_ACTION_ERROR_CODE, String.class));
117            COORD_ACTION.put("getErrorMessage", new Property(JsonTags.COORDINATOR_ACTION_ERROR_MESSAGE, String.class));
118            COORD_ACTION.put("toString", new Property(JsonTags.TO_STRING, String.class));
119    
120            COORD_JOB.put("getAppPath", new Property(JsonTags.COORDINATOR_JOB_PATH, String.class));
121            COORD_JOB.put("getAppName", new Property(JsonTags.COORDINATOR_JOB_NAME, String.class));
122            COORD_JOB.put("getId", new Property(JsonTags.COORDINATOR_JOB_ID, String.class));
123            COORD_JOB.put("getConf", new Property(JsonTags.COORDINATOR_JOB_CONF, String.class));
124            COORD_JOB.put("getStatus", new Property(JsonTags.COORDINATOR_JOB_STATUS, CoordinatorJob.Status.class));
125            COORD_JOB.put("getExecutionOrder",
126                          new Property(JsonTags.COORDINATOR_JOB_EXECUTIONPOLICY, CoordinatorJob.Execution.class));
127            COORD_JOB.put("getFrequency", new Property(JsonTags.COORDINATOR_JOB_FREQUENCY, Integer.TYPE));
128            COORD_JOB.put("getTimeUnit", new Property(JsonTags.COORDINATOR_JOB_TIMEUNIT, CoordinatorJob.Timeunit.class));
129            COORD_JOB.put("getTimeZone", new Property(JsonTags.COORDINATOR_JOB_TIMEZONE, String.class));
130            COORD_JOB.put("getConcurrency", new Property(JsonTags.COORDINATOR_JOB_CONCURRENCY, Integer.TYPE));
131            COORD_JOB.put("getTimeout", new Property(JsonTags.COORDINATOR_JOB_TIMEOUT, Integer.TYPE));
132            COORD_JOB.put("getLastActionTime", new Property(JsonTags.COORDINATOR_JOB_LAST_ACTION_TIME, Date.class));
133            COORD_JOB.put("getNextMaterializedTime",
134                          new Property(JsonTags.COORDINATOR_JOB_NEXT_MATERIALIZED_TIME, Date.class));
135            COORD_JOB.put("getStartTime", new Property(JsonTags.COORDINATOR_JOB_START_TIME, Date.class));
136            COORD_JOB.put("getEndTime", new Property(JsonTags.COORDINATOR_JOB_END_TIME, Date.class));
137            COORD_JOB.put("getUser", new Property(JsonTags.COORDINATOR_JOB_USER, String.class));
138            COORD_JOB.put("getGroup", new Property(JsonTags.COORDINATOR_JOB_GROUP, String.class));
139            COORD_JOB.put("getConsoleUrl", new Property(JsonTags.COORDINATOR_JOB_CONSOLE_URL, String.class));
140            COORD_JOB.put("getActions", new Property(JsonTags.COORDINATOR_ACTIONS, CoordinatorAction.class, true));
141            COORD_JOB.put("toString", new Property(JsonTags.TO_STRING, String.class));
142    
143            BUNDLE_JOB.put("getActions", new Property(JsonTags.COORDINATOR_ACTIONS, CoordinatorAction.class, true));
144    
145            BUNDLE_JOB.put("getAppPath",new Property(JsonTags.BUNDLE_JOB_PATH, String.class));
146            BUNDLE_JOB.put("getAppName",new Property(JsonTags.BUNDLE_JOB_NAME, String.class));
147            BUNDLE_JOB.put("getId",new Property(JsonTags.BUNDLE_JOB_ID, String.class));
148            BUNDLE_JOB.put("getExternalId",new Property(JsonTags.BUNDLE_JOB_EXTERNAL_ID, String.class));
149            BUNDLE_JOB.put("getConf",new Property(JsonTags.BUNDLE_JOB_CONF, String.class));
150            BUNDLE_JOB.put("getStatus",new Property(JsonTags.BUNDLE_JOB_STATUS, BundleJob.Status.class));
151            BUNDLE_JOB.put("getTimeUnit",new Property(JsonTags.BUNDLE_JOB_TIMEUNIT, BundleJob.Timeunit.class));
152            BUNDLE_JOB.put("getTimeout",new Property(JsonTags.BUNDLE_JOB_TIMEOUT, Integer.TYPE));
153            BUNDLE_JOB.put("getKickoffTime",new Property(JsonTags.BUNDLE_JOB_KICKOFF_TIME, Date.class));
154            BUNDLE_JOB.put("getStartTime",new Property(JsonTags.BUNDLE_JOB_START_TIME, Date.class));
155            BUNDLE_JOB.put("getEndTime",new Property(JsonTags.BUNDLE_JOB_END_TIME, Date.class));
156            BUNDLE_JOB.put("getPauseTime",new Property(JsonTags.BUNDLE_JOB_PAUSE_TIME, Date.class));
157            BUNDLE_JOB.put("getCreatedTime",new Property(JsonTags.BUNDLE_JOB_CREATED_TIME, Date.class));
158            BUNDLE_JOB.put("getUser",new Property(JsonTags.BUNDLE_JOB_USER, String.class));
159            BUNDLE_JOB.put("getGroup",new Property(JsonTags.BUNDLE_JOB_GROUP, String.class));
160            BUNDLE_JOB.put("getConsoleUrl",new Property(JsonTags.BUNDLE_JOB_CONSOLE_URL, String.class));
161            BUNDLE_JOB.put("getCoordinators",new Property(JsonTags.BUNDLE_COORDINATOR_JOBS, CoordinatorJob.class, true));
162            BUNDLE_JOB.put("toString", new Property(JsonTags.TO_STRING, String.class));
163        }
164    
165        /**
166         * The dynamic proxy invocation handler used to convert JSON values to bean properties using a mapping.
167         */
168        private static class JsonInvocationHandler implements InvocationHandler {
169            private final Map<String, Property> mapping;
170            private final JSONObject json;
171    
172            /**
173             * Invocation handler constructor.
174             *
175             * @param mapping property to JSON/type-info mapping.
176             * @param json the json object to back the property values.
177             */
178            public JsonInvocationHandler(Map<String, Property> mapping, JSONObject json) {
179                this.mapping = mapping;
180                this.json = json;
181            }
182    
183            @Override
184            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
185                Property prop = mapping.get(method.getName());
186                if (prop == null) {
187                    throw new RuntimeException("Undefined method mapping: " + method.getName());
188                }
189                if (prop.isList) {
190                    if (prop.type == WorkflowAction.class) {
191                        return createWorkflowActionList((JSONArray) json.get(prop.label));
192                    }
193                    else if (prop.type == CoordinatorAction.class) {
194                        return createCoordinatorActionList((JSONArray) json.get(prop.label));
195                    }
196                    else if (prop.type == CoordinatorJob.class) {
197                        return createCoordinatorJobList((JSONArray) json.get(prop.label));
198                    }
199                    else {
200                        throw new RuntimeException("Unsupported list type : " + prop.type.getSimpleName());
201                    }
202                }
203                else {
204                    return parseType(prop.type, json.get(prop.label));
205                }
206            }
207    
208            @SuppressWarnings("unchecked")
209            private Object parseType(Class type, Object obj) {
210                if (type == String.class) {
211                    return obj;
212                }
213                else if (type == Integer.TYPE) {
214                    return (obj != null) ? new Integer(((Long) obj).intValue()) : new Integer(0);
215                }
216                else if (type == Long.TYPE) {
217                    return (obj != null) ? obj : new Long(0);
218                }
219                else if (type == Date.class) {
220                    return JsonUtils.parseDateRfc822((String) obj);
221                }
222                else if (type.isEnum()) {
223                    return Enum.valueOf(type, (String) obj);
224                }
225                else if (type == WorkflowAction.class) {
226                    return createWorkflowAction((JSONObject) obj);
227                }
228                else {
229                    throw new RuntimeException("Unsupported type : " + type.getSimpleName());
230                }
231            }
232        }
233    
234        /**
235         * Creates a workflow action bean from a JSON object.
236         *
237         * @param json json object.
238         * @return a workflow action bean populated with the JSON object values.
239         */
240        public static WorkflowAction createWorkflowAction(JSONObject json) {
241            return (WorkflowAction) Proxy.newProxyInstance(JsonToBean.class.getClassLoader(),
242                                                           new Class[]{WorkflowAction.class},
243                                                           new JsonInvocationHandler(WF_ACTION, json));
244        }
245    
246        /**
247         * Creates a list of workflow action beans from a JSON array.
248         *
249         * @param json json array.
250         * @return a list of workflow action beans from a JSON array.
251         */
252        public static List<WorkflowAction> createWorkflowActionList(JSONArray json) {
253            List<WorkflowAction> list = new ArrayList<WorkflowAction>();
254            for (Object obj : json) {
255                list.add(createWorkflowAction((JSONObject) obj));
256            }
257            return list;
258        }
259    
260        /**
261         * Creates a workflow job bean from a JSON object.
262         *
263         * @param json json object.
264         * @return a workflow job bean populated with the JSON object values.
265         */
266        public static WorkflowJob createWorkflowJob(JSONObject json) {
267            return (WorkflowJob) Proxy.newProxyInstance(JsonToBean.class.getClassLoader(),
268                                                        new Class[]{WorkflowJob.class},
269                                                        new JsonInvocationHandler(WF_JOB, json));
270        }
271    
272        /**
273         * Creates a list of workflow job beans from a JSON array.
274         *
275         * @param json json array.
276         * @return a list of workflow job beans from a JSON array.
277         */
278        public static List<WorkflowJob> createWorkflowJobList(JSONArray json) {
279            List<WorkflowJob> list = new ArrayList<WorkflowJob>();
280            for (Object obj : json) {
281                list.add(createWorkflowJob((JSONObject) obj));
282            }
283            return list;
284        }
285    
286        /**
287         * Creates a coordinator action bean from a JSON object.
288         *
289         * @param json json object.
290         * @return a coordinator action bean populated with the JSON object values.
291         */
292        public static CoordinatorAction createCoordinatorAction(JSONObject json) {
293            return (CoordinatorAction) Proxy.newProxyInstance(JsonToBean.class.getClassLoader(),
294                                                              new Class[]{CoordinatorAction.class},
295                                                              new JsonInvocationHandler(COORD_ACTION, json));
296        }
297    
298        /**
299         * Creates a list of coordinator action beans from a JSON array.
300         *
301         * @param json json array.
302         * @return a list of coordinator action beans from a JSON array.
303         */
304        public static List<CoordinatorAction> createCoordinatorActionList(JSONArray json) {
305            List<CoordinatorAction> list = new ArrayList<CoordinatorAction>();
306            for (Object obj : json) {
307                list.add(createCoordinatorAction((JSONObject) obj));
308            }
309            return list;
310        }
311    
312        /**
313         * Creates a coordinator job bean from a JSON object.
314         *
315         * @param json json object.
316         * @return a coordinator job bean populated with the JSON object values.
317         */
318        public static CoordinatorJob createCoordinatorJob(JSONObject json) {
319            return (CoordinatorJob) Proxy.newProxyInstance(JsonToBean.class.getClassLoader(),
320                                                           new Class[]{CoordinatorJob.class},
321                                                           new JsonInvocationHandler(COORD_JOB, json));
322        }
323    
324        /**
325         * Creates a list of coordinator job beans from a JSON array.
326         *
327         * @param json json array.
328         * @return a list of coordinator job beans from a JSON array.
329         */
330        public static List<CoordinatorJob> createCoordinatorJobList(JSONArray json) {
331            List<CoordinatorJob> list = new ArrayList<CoordinatorJob>();
332            for (Object obj : json) {
333                list.add(createCoordinatorJob((JSONObject) obj));
334            }
335            return list;
336        }
337    
338        /**
339         * Creates a bundle job bean from a JSON object.
340         *
341         * @param json json object.
342         * @return a bundle job bean populated with the JSON object values.
343         */
344        public static BundleJob createBundleJob(JSONObject json) {
345            return (BundleJob) Proxy.newProxyInstance(JsonToBean.class.getClassLoader(),
346                                                           new Class[]{BundleJob.class},
347                                                           new JsonInvocationHandler(BUNDLE_JOB, json));
348        }
349    
350        /**
351         * Creates a list of bundle job beans from a JSON array.
352         *
353         * @param json json array.
354         * @return a list of bundle job beans from a JSON array.
355         */
356        public static List<BundleJob> createBundleJobList(JSONArray json) {
357            List<BundleJob> list = new ArrayList<BundleJob>();
358            for (Object obj : json) {
359                list.add(createBundleJob((JSONObject) obj));
360            }
361            return list;
362        }
363    }