/* * Copyright 2011 cruxframework.org. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package org.cruxframework.crux.core.rebind.screen; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cruxframework.crux.core.client.Legacy; import org.cruxframework.crux.core.client.utils.StringUtils; import org.cruxframework.crux.core.declarativeui.ViewParser; import org.cruxframework.crux.core.declarativeui.ViewProcessor; import org.cruxframework.crux.core.rebind.CruxGeneratorException; import org.cruxframework.crux.core.rebind.context.RebindContext; import org.cruxframework.crux.core.rebind.dataprovider.DataProviderType; import org.cruxframework.crux.core.utils.RegexpPatterns; import org.cruxframework.crux.core.utils.StreamUtils; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.w3c.dom.Document; import com.google.gwt.dev.resource.Resource; /** * Creates a representation for Crux views * * @author Thiago Bustamante * */ public class ViewFactory { private static final Log logger = LogFactory.getLog(ViewFactory.class); private Map<String, View> cache = new HashMap<String, View>(); private RebindContext context; private ViewProcessor viewProcessor; /** * Default Constructor */ public ViewFactory(RebindContext context) { this.context = context; this.viewProcessor = new ViewProcessor(context.getScreenLoader().getViewLoader()); } /** * Factory method for views. * @param id viewId * @param device device property for this permutation being compiled * @return * @throws ScreenConfigException */ public View getView(String id, String device) throws ScreenConfigException { String cacheKey = id + "_" + device; if (cache.containsKey(cacheKey)) { return cache.get(cacheKey); } Resource resource = context.getScreenLoader().getViewLoader().getView(id); if (resource == null) { throw new ScreenConfigException("View ["+id+"] not found!"); } InputStream inputStream; try { inputStream = resource.openContents(); } catch (IOException e) { throw new ScreenConfigException("View ["+id+"] not found!"); } Document viewDoc = viewProcessor.getView(inputStream, id, device); StreamUtils.safeCloseStream(inputStream); View view = getView(id, device, viewDoc, resource.getLastModified(), false); cache.put(cacheKey, view); return view; } /** * Factory method for views. * @param id * @param device * @param view * @param rootView * @return * @throws ScreenConfigException */ public View getView(String id, String device, Document view, long lastModified, boolean rootView) throws ScreenConfigException { try { JSONObject metadata = viewProcessor.extractWidgetsMetadata(id, device, view, rootView); View result = parseView(id, metadata, rootView); result.setLastModified(lastModified); return result; } catch (Exception e) { throw new ScreenConfigException("Error retrieving view ["+id+"].", e); } } /** * * @param id * @param device * @param stream * @return * @throws ScreenConfigException */ public Document getViewDocument(String id, String device, InputStream stream) throws ScreenConfigException { return viewProcessor.getView(stream, id, device); } public List<String> getViews() { return context.getScreenLoader().getViewLoader().getViews(); } protected void generateHTML(String viewId, String device, Document view, OutputStream out) { viewProcessor.generateHTML(viewId, device, view, out); } /** * Creates a widget based in its metadata information. * * @param elem * @param view * @return * @throws ScreenConfigException */ private Widget createWidget(JSONObject elem, View view) throws ScreenConfigException { if (!elem.has("id")) { throw new CruxGeneratorException("The id attribute is required for CRUX Widgets. " + "On view ["+view.getId()+"], there is an widget of type ["+elem.optString("_type")+"] without id."); } String widgetId; try { widgetId = elem.getString("id"); } catch (JSONException e) { throw new CruxGeneratorException("The id attribute is required for CRUX Widgets. " + "On view ["+view.getId()+"], there is an widget of type ["+elem.optString("_type")+"] without a valid id."); } Widget widget = view.getWidget(widgetId); if (widget != null) { throw new ScreenConfigException("Error creating widget. Duplicated identifier ["+widgetId+"]."); } widget = newWidget(elem, widgetId); if (widget == null) { throw new ScreenConfigException("Can not create widget ["+widgetId+"]. Verify the widget type."); } view.addWidget(widget); createWidgetChildren(elem, view, widgetId, widget); return widget; } /** * @param elem * @param view * @param widgetId * @param widget * @throws ScreenConfigException */ private void createWidgetChildren(JSONObject elem, View view, String widgetId, Widget widget) throws ScreenConfigException { if (elem.has("_children")) { try { JSONArray children = elem.getJSONArray("_children"); if (children != null) { for (int i=0; i< children.length(); i++) { JSONObject childElem = children.getJSONObject(i); if (isValidWidget(childElem)) { Widget child = createWidget(childElem, view); child.setParent(widget); } else if (isScreenDefinition(childElem)) { parseViewElement(view,childElem); } else if (isDataProviderDefinition(childElem)) { parseDataProviderElement(view, childElem); } } } } catch (JSONException e) { throw new ScreenConfigException("Can not create widget ["+widgetId+"]. Verify the widget type.", e); } } } /** * Test if a target json object represents a View definition for Crux. * @param cruxObject * @return * @throws JSONException */ private static boolean isScreenDefinition(JSONObject cruxObject) throws JSONException { if (cruxObject.has("_type")) { String type = cruxObject.getString("_type"); return (type != null && ViewParser.SCREEN_TYPE.equals(type)); } return false; } private static boolean isDataProviderDefinition(JSONObject cruxObject) throws JSONException { if (cruxObject.has("_type")) { String type = cruxObject.getString("_type"); return DataProviderType.isDataProviderType(type); } return false; } /** * Builds a new widget, based on its metadata. * @param elem * @param widgetId * @return * @throws ScreenConfigException */ private Widget newWidget(JSONObject elem, String widgetId) throws ScreenConfigException { try { String type = elem.getString("_type"); Widget widget = new Widget(elem); widget.setId(widgetId); widget.setType(type); return widget; } catch (Throwable e) { throw new ScreenConfigException("Can not create widget ["+widgetId+"]. Verify the widget type.", e); } } /** * Parse the HTML page and build the Crux View. * @param id * @param metaData * @return * @throws IOException * @throws ScreenConfigException */ private View parseView(String id, JSONObject metaData, boolean rootView) throws IOException, ScreenConfigException { try { JSONArray elementsMetadata = metaData.getJSONArray("elements"); JSONObject lazyDependencies = metaData.getJSONObject("lazyDeps"); String html = metaData.getString("_html"); JSONArray nativeControllers = metaData.getJSONArray("nativeControllers"); JSONArray nativeBindings = metaData.getJSONArray("nativeBindings"); View view = new View(id, lazyDependencies, html, rootView); int length = nativeControllers.length(); for (int i = 0; i < length; i++) { JSONObject nativeController = nativeControllers.getJSONObject(i); String method = nativeController.getString("method"); String controllerCall = nativeController.getString("controllerCall"); view.addNativeControllerCall(method, controllerCall); } length = nativeBindings.length(); for (int i = 0; i < length; i++) { JSONObject nativeBinding = nativeBindings.getJSONObject(i); String elementId = nativeBinding.getString("elementId"); String dataBinding = nativeBinding.getString("dataBinding"); String attributeName = nativeBinding.getString("attributeName"); view.addNativeDataBinding(elementId, dataBinding, attributeName); } length = elementsMetadata.length(); for (int i = 0; i < length; i++) { JSONObject compCandidate = elementsMetadata.getJSONObject(i); if (isScreenDefinition(compCandidate)) { parseViewElement(view,compCandidate); } else if (isDataProviderDefinition(compCandidate)) { parseDataProviderElement(view,compCandidate); } else if (isValidWidget(compCandidate)) { try { createWidget(compCandidate, view); } catch (ScreenConfigException e) { throw new ScreenConfigException("Error creating widget on view ["+id+"].", e); } } } return view; } catch (JSONException e) { throw new ScreenConfigException("Error parsing view ["+id+"].", e); } } private void parseDataProviderElement(View view, JSONObject elem) throws ScreenConfigException { if (!elem.has("id")) { throw new CruxGeneratorException("The id attribute is required for CRUX DataProviders. " + "On view ["+view.getId()+"], there is a dataProvider of type ["+elem.optString("_type")+"] without id."); } String id; try { id = elem.getString("id"); } catch (JSONException e) { throw new CruxGeneratorException("The id attribute is required for CRUX DataProviders. " + "On view ["+view.getId()+"], there is a dataProvider of type ["+elem.optString("_type")+"] without a valid id."); } DataProviderType type = DataProviderType.fromType(elem.optString("_type")); DataProvider dataProvider = new DataProvider(elem, id, type); view.addDataProvider(dataProvider); } /** * Parse view element * @param view * @param elem * @throws ScreenConfigException */ private void parseViewElement(View view, JSONObject elem) throws ScreenConfigException { try { view.setViewElement(elem); String[] attributes = JSONObject.getNames(elem); int length = attributes.length; for (int i = 0; i < length; i++) { String attrName = attributes[i]; if(attrName.equals("useController")) { parseViewUseControllerAttribute(view, elem); } else if(attrName.equals("useResource")) { parseViewUseResourceAttribute(view, elem); } else if(attrName.equals("useFormatter")) { parseViewUseFormatterAttribute(view, elem); } else if(attrName.equals("useDataSource")) { parseViewUseDatasourceAttribute(view, elem); } else if(attrName.equals("useView")) { //Ignore attribute TODO remove this } else if (attrName.equals("width")) { view.setWidth(elem.getString(attrName)); } else if (attrName.equals("styleName")) { view.setStyleName(elem.getString(attrName)); } else if(attrName.equals("height")) { view.setHeight(elem.getString(attrName)); } else if(attrName.equals("smallViewport")) { view.setSmallViewport(elem.getString(attrName)); } else if(attrName.equals("largeViewport")) { view.setLargeViewport(elem.getString(attrName)); } else if(attrName.equals("disableRefresh")) { view.setDisableRefresh(elem.getBoolean(attrName)); } else if (attrName.startsWith("on")) { Event event = EventFactory.getEvent(attrName, elem.getString(attrName)); if (event != null) { view.addEvent(event); } } else if (attrName.equals("title")) { String title = elem.getString(attrName); if (title != null && title.length() > 0) { view.setTitle(title); } } else if (attrName.equals("fragment")) { String fragment = elem.getString(attrName); if (fragment != null && fragment.length() > 0) { view.setFragment(fragment); } } else if (attrName.equals("dataObject")) { String dataObject = elem.getString(attrName); if (dataObject != null && dataObject.length() > 0) { view.setDataObject(dataObject); } } else if (!attrName.equals("id") && !attrName.equals("_type")) { if (logger.isInfoEnabled()) logger.info("Error setting property ["+attrName+"] for view ["+view.getId()+"]."); } } } catch (JSONException e) { throw new ScreenConfigException("Error parsing view metaData. View ["+view.getId()+"]."); } } /** * @param view * @param elem * @throws ScreenConfigException */ /** * @param view * @param elem * @throws ScreenConfigException */ private void parseViewUseControllerAttribute(View view, JSONObject elem) throws ScreenConfigException { String handlerStr; try { handlerStr = elem.getString("useController"); } catch (JSONException e) { throw new ScreenConfigException(e); } if (handlerStr != null) { String[] handlers = RegexpPatterns.REGEXP_COMMA.split(handlerStr); for (String handler : handlers) { handler = handler.trim(); if (!StringUtils.isEmpty(handler)) { if (!context.getControllers().hasController(handler)) { throw new ScreenConfigException("Controller ["+handler+"], declared on view ["+view.getId()+"], not found!"); } view.addController(handler); } } } } /** * @param view * @param elem * @throws ScreenConfigException */ @Deprecated @Legacy private void parseViewUseDatasourceAttribute(View view, JSONObject elem) throws ScreenConfigException { String datasourceStr; try { datasourceStr = elem.getString("useDataSource"); } catch (JSONException e) { throw new ScreenConfigException(e); } if (datasourceStr != null) { String[] datasources = RegexpPatterns.REGEXP_COMMA.split(datasourceStr); for (String datasource : datasources) { datasource = datasource.trim(); if (!StringUtils.isEmpty(datasource)) { if (!context.getDataSources().hasDataSource(datasource)) { throw new ScreenConfigException("Datasource ["+datasource+"], declared on view ["+view.getId()+"], not found!"); } view.addDataSource(datasource); } } } } /** * @param view * @param elem * @throws ScreenConfigException */ @Deprecated @Legacy private void parseViewUseFormatterAttribute(View view, JSONObject elem) throws ScreenConfigException { String formatterStr; try { formatterStr = elem.getString("useFormatter"); } catch (JSONException e) { throw new ScreenConfigException(e); } if (formatterStr != null) { String[] formatters = RegexpPatterns.REGEXP_COMMA.split(formatterStr); for (String formatter : formatters) { formatter = formatter.trim(); if (!StringUtils.isEmpty(formatter)) { if (context.getFormatters().getFormatter(formatter) == null) { throw new ScreenConfigException("Formatter ["+formatter+"], declared on view ["+view.getId()+"], not found!"); } view.addFormatter(formatter); } } } } /** * @param view * @param elem * @throws ScreenConfigException */ private void parseViewUseResourceAttribute(View view, JSONObject elem) throws ScreenConfigException { String handlerStr; try { handlerStr = elem.getString("useResource"); } catch (JSONException e) { throw new ScreenConfigException(e); } if (handlerStr != null) { String[] handlers = RegexpPatterns.REGEXP_COMMA.split(handlerStr); for (String res : handlers) { res = res.trim(); if (!StringUtils.isEmpty(res)) { if (!context.getResources().hasResource(res)) { throw new ScreenConfigException("Resource ["+res+"], declared on view ["+view.getId()+"], not found!"); } view.addResource(res); } } } } /** * Test if a target json object represents a widget definition for Crux. * @param cruxObject * @return */ public static boolean isValidWidget(JSONObject cruxObject) { if (cruxObject.has("_type")) { String type = cruxObject.optString("_type"); return (type != null && !ViewParser.SCREEN_TYPE.equals(type) && !DataProviderType.isDataProviderType(type)); } return false; } }