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