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