/**
* ***************************************************************************
* Copyright (c) 2010 Qcadoo Limited
* Project: Qcadoo Framework
* Version: 1.4
*
* This file is part of Qcadoo.
*
* Qcadoo is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation; either version 3 of the License,
* or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
* ***************************************************************************
*/
package com.qcadoo.view.internal.internal;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.qcadoo.localization.api.TranslationService;
import com.qcadoo.model.api.DataDefinition;
import com.qcadoo.security.api.SecurityRole;
import com.qcadoo.view.api.ViewDefinitionState;
import com.qcadoo.view.api.ribbon.RibbonActionItem.Type;
import com.qcadoo.view.internal.api.ComponentPattern;
import com.qcadoo.view.internal.api.ContainerPattern;
import com.qcadoo.view.internal.api.InternalViewDefinition;
import com.qcadoo.view.internal.api.InternalViewDefinitionService;
import com.qcadoo.view.internal.components.window.WindowComponentPattern;
import com.qcadoo.view.internal.hooks.AbstractViewHookDefinition;
import com.qcadoo.view.internal.hooks.HookType;
import com.qcadoo.view.internal.patterns.AbstractComponentPattern;
import com.qcadoo.view.internal.ribbon.model.*;
import com.qcadoo.view.internal.states.AbstractComponentState;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.util.StringUtils;
import java.util.*;
public final class ViewDefinitionImpl implements InternalViewDefinition {
private final String name;
private final String pluginIdentifier;
private final SecurityRole authorizationRole;
private final DataDefinition dataDefinition;
private final boolean menuAccessible;
private Integer windowWidth;
private Integer windowHeight;
private final ViewHooksHolder viewHooksHolder;
private final Set<String> jsFilePaths = Sets.newHashSet();
private final Map<String, ComponentPattern> patterns = Maps.newLinkedHashMap();
private final Map<String, ComponentPattern> registry = Maps.newLinkedHashMap();
private final TranslationService translationService;
private boolean alreadyHasNavigation;
private boolean permanentlyDisabled;
private RibbonGroupsPack ribbonNavigationGroupPack;
private JSONObject jsonContext;
public ViewDefinitionImpl(final String name, final String pluginIdentifier, final DataDefinition dataDefinition,
final boolean menuAccessible, final TranslationService translationService) {
this(name, pluginIdentifier, null, dataDefinition, menuAccessible, translationService);
}
public ViewDefinitionImpl(final String name, final String pluginIdentifier, final SecurityRole authorizationRole,
final DataDefinition dataDefinition, final boolean menuAccessible, final TranslationService translationService) {
this.name = name;
this.authorizationRole = authorizationRole;
this.dataDefinition = dataDefinition;
this.pluginIdentifier = pluginIdentifier;
this.menuAccessible = menuAccessible;
this.translationService = translationService;
this.viewHooksHolder = new ViewHooksHolder();
}
public void setWindowDimmension(final Integer windowWidth, final Integer windowHeight) {
this.windowWidth = windowWidth;
this.windowHeight = windowHeight;
}
@Override
public void initialize() {
List<ComponentPattern> list = getPatternsAsList(patterns.values());
int lastNotInitialized = 0;
while (true) {
int notInitialized = 0;
for (ComponentPattern pattern : list) {
if (!pattern.initialize()) {
notInitialized++;
}
}
if (notInitialized == 0) {
break;
}
if (notInitialized == lastNotInitialized) {
throw new IllegalStateException("There is cyclic dependency between components");
}
lastNotInitialized = notInitialized;
}
initAdditionalNavigation();
}
@Override
public Map<String, Object> prepareView(final JSONObject jsonObject, final Locale locale) {
viewHooksHolder.callConstructionHooks(this, jsonObject, locale);
Map<String, Object> model = Maps.newHashMap();
Map<String, Object> childrenModels = Maps.newHashMap();
toggleAdditionalNavigationGroup(getBooleanFromJson(jsonObject, "window.showBack"));
permanentlyDisabled = getBooleanFromJson(jsonObject, "window." + AbstractComponentState.JSON_PERMANENTLY_DISABLED);
toggleRibbonPermanentlyDisabled();
for (ComponentPattern componentPattern : patterns.values()) {
childrenModels.put(componentPattern.getName(), componentPattern.prepareView(locale));
}
model.put(JSON_COMPONENTS, childrenModels);
model.put(JSON_JS_FILE_PATHS, getJsFilePaths());
model.put("hasDataDefinition", getDataDefinition() != null);
try {
JSONObject json = new JSONObject();
JSONObject translations = new JSONObject();
translations.put("backWithChangesConfirmation",
translationService.translate("qcadooView.backWithChangesConfirmation", locale));
json.put("translations", translations);
if (windowWidth != null) {
json.put("windowWidth", windowWidth);
}
if (windowHeight != null) {
json.put("windowHeight", windowHeight);
}
model.put("jsOptions", json);
} catch (JSONException e) {
throw new IllegalStateException(e.getMessage(), e);
}
return model;
}
private boolean getBooleanFromJson(final JSONObject jsonObject, final String fieldName) {
try {
return jsonObject.has(fieldName) && !jsonObject.isNull(fieldName) && jsonObject.getBoolean(fieldName);
} catch (JSONException e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
private InternalRibbon getRibbon() {
ComponentPattern window = patterns.get("window");
if (window instanceof WindowComponentPattern) {
return ((WindowComponentPattern) window).getRibbon();
}
return null;
}
private void initAdditionalNavigation() {
InternalRibbon ribbon = getRibbon();
if (ribbon == null || ribbon.getGroupByName("navigation") != null) {
alreadyHasNavigation = ribbon != null;
return;
}
InternalRibbonActionItem backButton = new RibbonActionItemImpl();
backButton.setName("back");
backButton.setIcon("backIcon24.png");
backButton.setAction("#{window}.performBack");
backButton.setEnabled(true);
backButton.setType(Type.BIG_BUTTON);
InternalRibbonGroup additionalNavigationGroup = new RibbonGroupImpl("navigation");
additionalNavigationGroup.addItem(backButton);
ribbonNavigationGroupPack = new SingleRibbonGroupPack(additionalNavigationGroup);
}
// TODO MAKU move additional navigation to template
private void toggleAdditionalNavigationGroup(final boolean showBack) {
InternalRibbon ribbon = getRibbon();
if (ribbon == null || alreadyHasNavigation) {
return;
}
if (!showBack) {
ribbon.removeGroupsPack(ribbonNavigationGroupPack);
return;
}
if (ribbon.getGroupByName("navigation") == null) {
ribbon.addGroupPackAsFirst(ribbonNavigationGroupPack);
}
}
private void toggleRibbonPermanentlyDisabled() {
InternalRibbon ribbon = getRibbon();
if (ribbon != null) {
ribbon.setPermanentlyDisabled(permanentlyDisabled);
}
}
@Override
public ViewDefinitionState performEvent(final JSONObject jsonObject, final Locale locale) throws JSONException {
viewHooksHolder.callConstructionHooks(this, jsonObject, locale);
ViewDefinitionStateImpl viewDefinitionState = new ViewDefinitionStateImpl();
viewDefinitionState.setTranslationService(translationService);
viewDefinitionState.setJsonContext(getJsonContext());
JSONObject eventJson = jsonObject.getJSONObject(JSON_EVENT);
String eventName = eventJson.getString(JSON_EVENT_NAME);
viewDefinitionState.setViewAfterReload(!(eventName.startsWith("initialize") || "reset".equals(eventName)));
viewDefinitionState.setViewAfterRedirect(eventName.startsWith("redirect") || eventName.startsWith("initialize"));
for (ComponentPattern cp : patterns.values()) {
viewDefinitionState.addChild(cp.createComponentState(viewDefinitionState));
}
viewHooksHolder.callLifecycleHooks(HookType.BEFORE_INITIALIZE, viewDefinitionState);
if (permanentlyDisabled) {
jsonObject.put(AbstractComponentState.JSON_PERMANENTLY_DISABLED, true);
}
viewDefinitionState.initialize(jsonObject, locale);
for (ComponentPattern cp : patterns.values()) {
((AbstractComponentPattern) cp).updateComponentStateListeners(viewDefinitionState);
}
viewHooksHolder.callLifecycleHooks(HookType.AFTER_INITIALIZE, viewDefinitionState);
String eventComponent = eventJson.has(JSON_EVENT_COMPONENT) ? eventJson.getString(JSON_EVENT_COMPONENT) : null;
JSONArray eventArgsArray = eventJson.has(JSON_EVENT_ARGS) ? eventJson.getJSONArray(JSON_EVENT_ARGS) : new JSONArray();
String[] eventArgs = new String[eventArgsArray.length()];
for (int i = 0; i < eventArgsArray.length(); i++) {
eventArgs[i] = eventArgsArray.getString(i);
}
viewDefinitionState.performEvent(eventComponent, eventName, eventArgs);
viewHooksHolder.callLifecycleHooks(HookType.BEFORE_RENDER, viewDefinitionState);
return viewDefinitionState;
}
public void registerViews(final InternalViewDefinitionService viewDefinitionService) {
for (ComponentPattern cp : patterns.values()) {
cp.registerViews(viewDefinitionService);
}
}
@Override
public void addComponentPattern(final ComponentPattern componentPattern) {
patterns.put(componentPattern.getName(), componentPattern);
}
@Override
public ComponentPattern getComponentByReference(final String reference) {
return registry.get(reference);
}
@Override
public void registerComponent(final String reference, final String path, final ComponentPattern pattern) {
if (registry.containsKey(reference)) {
throw new IllegalStateException("Duplicated pattern reference '" + reference + "'");
}
registry.put(reference, pattern);
}
@Override
public void unregisterComponent(final String reference, final String path) {
registry.remove(reference);
}
@Override
public String getName() {
return name;
}
@Override
public String getPluginIdentifier() {
return pluginIdentifier;
}
@Override
public boolean isMenuAccessible() {
return menuAccessible;
}
@Override
public DataDefinition getDataDefinition() {
return dataDefinition;
};
public Set<String> getJsFilePaths() {
return jsFilePaths;
}
@Override
public void addJsFilePath(final String jsFilePath) {
jsFilePaths.add(jsFilePath);
}
@Override
public void addHook(final AbstractViewHookDefinition viewHook) {
viewHooksHolder.addHook(viewHook);
}
@Override
public void removeHook(final AbstractViewHookDefinition viewHook) {
viewHooksHolder.removeHook(viewHook);
}
private List<ComponentPattern> getPatternsAsList(final Collection<ComponentPattern> patterns) {
List<ComponentPattern> list = new ArrayList<ComponentPattern>();
list.addAll(patterns);
for (ComponentPattern pattern : patterns) {
if (pattern instanceof ContainerPattern) {
list.addAll(getPatternsAsList(((ContainerPattern) pattern).getChildren().values()));
}
}
return list;
}
@Override
public WindowComponentPattern getRootWindow() {
if (patterns.size() != 1) {
return null;
}
ComponentPattern rootPattern = patterns.values().iterator().next();
if (rootPattern instanceof WindowComponentPattern) {
return (WindowComponentPattern) rootPattern;
}
return null;
}
@Override
@SuppressWarnings("unchecked")
public String translateContextReferences(final String context) {
if (context == null) {
return null;
}
try {
JSONObject oldContext = new JSONObject(context);
JSONObject newContext = new JSONObject();
Iterator<String> paths = oldContext.keys();
while (paths.hasNext()) {
String oldPath = paths.next();
String[] newPath = oldPath.split("\\.");
ComponentPattern pattern = getComponentByReference(newPath[0]);
if (pattern == null) {
throw new IllegalStateException("Cannot find component for " + getPluginIdentifier() + "." + getName() + ": "
+ newPath[0]);
}
newPath[0] = pattern.getPath();
newContext.put(StringUtils.arrayToDelimitedString(newPath, "."), oldContext.get(oldPath));
}
return newContext.toString();
} catch (JSONException e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
@Override
public SecurityRole getAuthorizationRole() {
return authorizationRole;
}
@Override
public JSONObject getJsonContext() {
return jsonContext;
}
@Override
public void setJsonContext(JSONObject jsonContext) {
this.jsonContext = jsonContext;
}
}