/**
* Axelor Business Solutions
*
* Copyright (C) 2016 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
package com.axelor.apps.admin.service;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import javax.xml.bind.JAXBException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.axelor.common.ClassUtils;
import com.axelor.common.Inflector;
import com.axelor.db.mapper.Mapper;
import com.axelor.db.mapper.Property;
import com.axelor.meta.MetaStore;
import com.axelor.meta.db.MetaAction;
import com.axelor.meta.db.MetaView;
import com.axelor.meta.db.repo.MetaViewRepository;
import com.axelor.meta.loader.XMLViews;
import com.axelor.meta.schema.ObjectViews;
import com.axelor.meta.schema.actions.ActionView;
import com.axelor.meta.schema.actions.ActionView.View;
import com.axelor.meta.schema.views.AbstractView;
import com.axelor.meta.schema.views.AbstractWidget;
import com.axelor.meta.schema.views.Button;
import com.axelor.meta.schema.views.ButtonGroup;
import com.axelor.meta.schema.views.Dashboard;
import com.axelor.meta.schema.views.Dashlet;
import com.axelor.meta.schema.views.Field;
import com.axelor.meta.schema.views.FormView;
import com.axelor.meta.schema.views.Label;
import com.axelor.meta.schema.views.Menu;
import com.axelor.meta.schema.views.Menu.Item;
import com.axelor.meta.schema.views.Panel;
import com.axelor.meta.schema.views.PanelEditor;
import com.axelor.meta.schema.views.PanelField;
import com.axelor.meta.schema.views.PanelInclude;
import com.axelor.meta.schema.views.PanelRelated;
import com.axelor.meta.schema.views.PanelTabs;
import com.axelor.meta.schema.views.Selection;
import com.axelor.meta.schema.views.Selection.Option;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.inject.Inject;
public class ViewDocXmlProcessor {
private final Logger log = LoggerFactory.getLogger(getClass());
private ViewDocExportService exportService;
private Map<String, Map<String,List<String>>> itemCheckMap = new HashMap<String, Map<String, List<String>>>();
private List<String> viewProcessed = new ArrayList<String>();
private Map<String, List<String[]>> o2mViewMap = new HashMap<String, List<String[]>>();
private Inflector inflector;
private boolean newForm = false;
@Inject
private MetaViewRepository metaViewRepo;
@Inject
public ViewDocXmlProcessor(ViewDocExportService exportService){
inflector = Inflector.getInstance();
this.exportService = exportService;
}
protected void processModel(String model, String[] view){
try{
if(view == null || (view[0].equals("form") && view[1] == null)){
view = new String[]{"form", getFormName(model)};
}
if(viewProcessed.contains(view[1])){
return;
}
if(view[1] == null){
log.debug("No view name for type: {}, model:{}", view[0], model);
return;
}
List<MetaView> metaViews = null;
Mapper mapper = null;
if(model != null){
mapper = Mapper.of(ClassUtils.findClass(model));
metaViews = metaViewRepo.all().filter(
"self.type = ? and self.model = ? and self.name = ?",
view[0], model, view[1]).fetch();
if(!itemCheckMap.containsKey(view[1])){
Map<String, List<String>> map = new HashMap<String, List<String>>();
itemCheckMap.put(view[1], map);
}
}
else{
metaViews = metaViewRepo.all().filter(
"self.type = ? and self.name = ?",
view[0], view[1]).fetch();
}
if(metaViews.isEmpty()){
log.debug("No view found: {}, model: {}", view[1], model);
}
else{
processView(metaViews.iterator(), mapper, view[0]);
addO2MViews(model);
}
o2mViewMap = new HashMap<String, List<String[]>>();
}
catch(IllegalArgumentException e){
log.debug("Model not found: {}", model);
}
}
protected void processModel(String model, MetaAction action){
String[] viewName = getViewName(action);
if(viewName != null || model != null){
processModel(model, getViewName(action));
}
else{
log.debug("No model or view for action: {}", action.getName());
}
}
private boolean isChecked(String view, String type, String item){
if(!itemCheckMap.containsKey(view)){
Map<String, List<String>> map = new HashMap<String, List<String>>();
map.put(type, new ArrayList<String>());
itemCheckMap.put(view, map);
}
Map<String, List<String>> map = itemCheckMap.get(view);
if(!map.containsKey(type)){
map.put(type, new ArrayList<String>());
}
List<String> checkList = map.get(type);
if(!checkList.contains(item)){
checkList.add(item);
return false;
}
else{
return true;
}
}
private String getFormName(String model){
String viewName = model.substring(model.lastIndexOf(".")+1);
viewName = inflector.underscore(viewName);
viewName = inflector.dasherize(viewName);
return viewName + "-form";
}
private String[] getViewName(MetaAction action){
try {
ObjectViews objectViews = XMLViews.fromXML(action.getXml());
ActionView actionView = (ActionView) objectViews.getActions().get(0);
for(View view : actionView.getViews()){
String type = view.getType();
String name = view.getName();
if(type.equals("form") || type.equals("dashboard")){
return new String[]{type, name};
}
}
} catch (JAXBException e) {
e.printStackTrace();
}
return null;
}
private void processView(Iterator<MetaView> viewIter, Mapper mapper, String type){
if(!viewIter.hasNext()){
return;
}
MetaView view = viewIter.next();
String name = view.getName();
if(!viewProcessed.contains(name)){
try {
ObjectViews views = XMLViews.fromXML(view.getXml());
switch(type){
case "form":
FormView form = (FormView) views.getViews().get(0);
newForm = true;
processForm(form, view.getModule(), mapper, true);
break;
case "dashboard":
if(!exportService.getOnlyPanel()){
Dashboard dashboard = (Dashboard) views.getViews().get(0);
processDashboard(dashboard, view.getModule());
}
break;
}
} catch (JAXBException e) {
e.printStackTrace();
}
}
processView(viewIter, mapper, type);
}
private void addO2MViews(String model){
o2mViewMap.remove(model);
Set<Entry<String, List<String[]>>> entrySet = new HashSet<Map.Entry<String,List<String[]>>>();
entrySet.addAll(o2mViewMap.entrySet());
for(Entry<String, List<String[]>> entry : entrySet){
List<String[]> views = entry.getValue();
String key = entry.getKey();
for(String[] view : views){
if(view[0] != null){
exportService.setMenuPath(view[0].split(","));
}
if(view[1] == null){
view[1] = getFormName(key);
}
processModel(key, new String[]{"form",view[1]});
// String[] menuPath = exportService.getMenuPath();
// if(menuPath != null){
exportService.setMenuPath(new String[]{"",""});
// }
}
}
}
private void processForm(FormView form, String module, Mapper mapper, boolean addPanel){
String name = form.getName();
log.debug("Processing form: {}", name);
if(!exportService.getOnlyPanel()){
List<Button> buttons = form.getToolbar();
if(buttons != null){
for(Button button : buttons){
processButton(button, name, module, mapper, false);
}
}
List<Menu> menus = form.getMenubar();
if(menus != null){
processMenuBarMenu(menus.iterator(), name, module, mapper);
}
}
List<AbstractWidget> items = form.getItems();
if(items != null){
processItems(items.iterator(), name, module, mapper, addPanel);
}
viewProcessed.add(name);
}
private void processMenuBarMenu(Iterator<Menu> menuIter, String view, String module, Mapper mapper){
if(!menuIter.hasNext()){
return;
}
Menu menu = menuIter.next();
String className = mapper.getBeanClass().getName();
String title = menu.getTitle();
if(!Strings.isNullOrEmpty(title) && !isChecked(view, "menu", title)){
String[] values = new String[]{
getModuleName(menu, module),
className,
view, "Toolbar Menu",
"",
exportService.translate(title, "en"),
exportService.translate(title, "fr"),
"",
""
};
exportService.writeRow(values, newForm, false, false);
newForm = false;
}
List<AbstractWidget> items = menu.getItems();
if(items != null){
processItems(items.iterator(), view, module, mapper, false);
}
processMenuBarMenu(menuIter, view, module, mapper);
}
private void processDashboard(Dashboard dashboard, String module){
List<AbstractWidget> items = dashboard.getItems();
if(items != null){
processItems(items.iterator(), dashboard.getName(), module, null, false);
}
String name = dashboard.getName();
// if(!itemCheckMap.containsKey(name)){
// Map<String, List<String>> map = new HashMap<String, List<String>>();
// itemCheckMap.put(name, map);
// }
viewProcessed.add(name);
}
private String getModuleName(AbstractWidget item, String module){
String moduleName = item.getModuleToCheck();
if(Strings.isNullOrEmpty(moduleName)){
moduleName = module;
}
return moduleName;
}
private void processItems(Iterator<AbstractWidget> itemIter, String view, String module, Mapper mapper, boolean addPanel){
if(!itemIter.hasNext()){
return;
}
AbstractWidget item = itemIter.next();
processElement(item, view, module, mapper, addPanel);
processItems(itemIter, view, module, mapper, addPanel);
}
private void processElement(AbstractWidget item, String view,
String module, Mapper mapper, boolean addPanel) {
Class<? extends AbstractWidget> klass = item.getClass();
String name = klass.getSimpleName();
if(exportService.getOnlyPanel()
&& !name.equals("Panel")
&& !name.equals("PanelTabs")
&& !name.equals("PanelInclude")){
return;
}
String methodName = "process" + name;
try {
Method method = getClass().getDeclaredMethod(methodName,
new Class[] {klass, String.class, String.class, Mapper.class, boolean.class});
method.setAccessible(true);
method.invoke(this, new Object[]{item, view, module, mapper, addPanel});
} catch (NoSuchMethodException | SecurityException
| IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
log.debug("No method found: {}", methodName);
// e.printStackTrace();
}
}
@SuppressWarnings("unused")
private void processPanel(Panel panel, String view, String module, Mapper mapper, boolean addPanel){
String panelType = "SubPanel";
if(addPanel){
panelType = "Panel";
}
String className = mapper.getBeanClass().getName();
String title = panel.getTitle();
String name = panel.getName();
String checkItem = name;
if(checkItem == null){
checkItem = title;
}
if(!Strings.isNullOrEmpty(checkItem) && !isChecked(view, "panel", checkItem)){
String[] values = new String[]{
getModuleName(panel, module),
className,
view,
panelType,
name,
exportService.translate(title, "en"),
exportService.translate(title, "fr"),
"",
""
};
exportService.writeRow(values, newForm, addPanel, !addPanel);
newForm = false;
}
processItems(panel.getItems().iterator(), view, module, mapper, false);
}
@SuppressWarnings("unused")
private void processPanelField(PanelField panelField, String view, String module, Mapper mapper, boolean addPanel){
processField(panelField, view, module, mapper, addPanel);
newForm = false;
processPanelEditor(panelField, view, module, mapper);
}
@SuppressWarnings("unused")
private void processPanelTabs(PanelTabs panelTabs, String view, String module, Mapper mapper, boolean addPanel){
processItems(panelTabs.getItems().iterator(), view, getModuleName(panelTabs, module), mapper, true);
}
private void processField(Field field, String view, String module, Mapper mapper, boolean addPanel){
String name = field.getName();
if(name.contains(".")){
return;
}
String title = field.getTitle();
String className = mapper.getBeanClass().getName();
if(isChecked(view, "field", name)){
return;
}
Property property = mapper.getProperty(name);
String type = field.getServerType();
String target = field.getTarget();
List<?> selectionList = field.getSelectionList();
if(property != null){
type = property.getType().name();
if(title == null){
title = property.getTitle();
}
String selection = property.getSelection();
if(selection != null && selectionList == null){
selectionList = MetaStore.getSelectionList(selection);
}
Class<?> targetClass = property.getTarget();
if(targetClass != null){
target = targetClass.getName();
}
}
if(target != null && type != null){
if(type.equals("ONE_TO_MANY") || type.equals("one-to-many")){
String parentView = view + "(" + exportService.translate(title, "en") + "),"
+ view + "(" + exportService.translate(title, "fr") + ")";
updateO2MViewMap(target, field.getFormView(), parentView);
}
String[] targets = target.split("\\.");
type = type + "(" + targets[targets.length - 1] + ")";
}
String moduleName = getModuleName(field, module);
if(name != null){
String selectEN = "";
String selectFR = "";
if(selectionList != null){
selectEN = updateSelect(selectionList, "en");
selectFR = updateSelect(selectionList, "fr");
}
if(Strings.isNullOrEmpty(type)){
type = "EMPTY";
}
String[] values = new String[]{moduleName,
className,
view,
type,
name,
exportService.translate(title,"en"),
exportService.translate(title, "fr"),
selectEN,
selectFR
};
exportService.writeRow(values, newForm, false, false);
newForm = false;
}
}
private void updateO2MViewMap(String className, String o2mView, String parentView){
List<String[]> views = o2mViewMap.get(className);
if(views == null){
views = new ArrayList<String[]>();
o2mViewMap.put(className, views);
}
if(!Strings.isNullOrEmpty(o2mView)
&& !views.contains(o2mView)
&& !viewProcessed.contains(o2mView)){
views.add(new String[]{parentView, o2mView});
}
else{
views.add(new String[]{parentView, null});
}
o2mViewMap.put(className, views);
}
private String updateSelect(List<?> selection, String lang){
List<String> titles = new ArrayList<String>();
for(Object object : selection){
Selection.Option option = (Option) object;
titles.add(exportService.translate(option.getTitle(), lang));
}
return Joiner.on(":").join(titles);
}
private void processPanelEditor(PanelField panelField, String view, String module, Mapper mapper) {
PanelEditor panelEditor = panelField.getEditor();
if(panelEditor != null){
String target = panelField.getTarget();
if(target != null){
newForm = true;
try{
Mapper targetMapper = Mapper.of(ClassUtils.findClass(target));
processItems(panelEditor.getItems().iterator(), view, getModuleName(panelField, module), targetMapper, false);
}catch(IllegalArgumentException e){
log.debug("Model not found: {}", target);
}
}
else{
processItems(panelEditor.getItems().iterator(), view, getModuleName(panelField, module), mapper, false);
}
}
}
@SuppressWarnings("unused")
private void processPanelInclude(PanelInclude panelInclude, String viewName, String module, Mapper mapper, boolean addPanel){
AbstractView view = panelInclude.getView();
if(view != null){
String name = view.getName();
if(!viewProcessed.contains(name)){
processForm((FormView)view, getModuleName(panelInclude, module), mapper, addPanel);
}
}
else{
log.debug("Issue in panel include: {}", panelInclude.getName());
}
}
@SuppressWarnings("unused")
private void processButtonGroup(ButtonGroup buttonGroup, String view, String module, Mapper mapper, boolean addPanel){
List<AbstractWidget> items = buttonGroup.getItems();
if(items != null){
processItems(items.iterator(), view, module, mapper, addPanel);
}
}
private void processButton(Button button, String view, String module, Mapper mapper, boolean addPanel){
String name = button.getName();
String className = mapper.getBeanClass().getName();
if(!isChecked(view, "button", name)){
String title = button.getTitle();
String[] values = new String[]{getModuleName(button, module),
className,
view,
"Button",
name,
exportService.translate(title, "en"),
exportService.translate(title, "fr"),
"", ""
};
exportService.writeRow(values, newForm, false, false);
newForm = false;
}
}
@SuppressWarnings("unused")
private void processPanelRelated(PanelRelated panelRelated, String view, String module, Mapper mapper, boolean addPanel){
String name = panelRelated.getName();
String title = panelRelated.getTitle();
String className = mapper.getBeanClass().getName();
if(isChecked(view, "field", name)){
return;
}
Property property = mapper.getProperty(name);
String type = panelRelated.getServerType();
String target = panelRelated.getTarget();
if(property != null){
type = property.getType().name();
if(title == null){
title = property.getTitle();
}
Class<?> targetClass = property.getTarget();
if(targetClass != null){
target = targetClass.getName();
}
}
else{
log.debug("No property found: {}, class: {}", name, className);
}
if(Strings.isNullOrEmpty(type)){
type = "EMPTY";
}
if(!Strings.isNullOrEmpty(target)){
if(type.equals("ONE_TO_MANY") || type.equals("one-to-many")){
String parentView = view + "(" + exportService.translate(title, "en") + "),"
+ view + "(" + exportService.translate(title, "fr") + ")";
updateO2MViewMap(target, panelRelated.getFormView(), parentView);
}
String[] targets = target.split("\\.");
type = type + "(" + targets[targets.length - 1] + ")";
}
String[] values = new String[]{getModuleName(panelRelated, module),
className,
view,
type,
name,
exportService.translate(title,"en"),
exportService.translate(title, "fr"),
"",
""
};
exportService.writeRow(values, newForm, false, false);
newForm = false;
}
@SuppressWarnings("unused")
private void processLabel(Label label, String view, String module, Mapper mapper, boolean addPanel){
String className = mapper.getBeanClass().getName();
String title = label.getTitle();
if(isChecked(view, "label", title)){
return;
}
String[] values = new String[]{getModuleName(label, module),
className,
view,
"Label",
label.getName(),
exportService.translate(title,"en"),
exportService.translate(title, "fr"),
"",
""
};
exportService.writeRow(values, newForm, false, false);
newForm = false;
}
@SuppressWarnings("unused")
private void processDashlet(Dashlet dashlet, String view, String module, Mapper mapper, boolean addPanel){
String title = dashlet.getTitle();
String action = dashlet.getAction();
String className = "";
if(title == null && action != null){
ActionView actionView = (ActionView) MetaStore.getAction(dashlet.getAction());
title = actionView.getTitle();
}
if(isChecked(view, "dashlet", title)){
return;
}
if(!Strings.isNullOrEmpty(title)){
String[] values = new String[]{
getModuleName(dashlet, module),
className,
view,
"Dashlet",
dashlet.getName(),
exportService.translate(title, "en"),
exportService.translate(title, "fr"),
"",
""
};
exportService.writeRow(values, newForm, false, false);
newForm = false;
}
}
@SuppressWarnings("unused")
private void processItem(Item item, String view, String module, Mapper mapper, boolean addPanel){
String className = mapper.getBeanClass().getName();
String title = item.getTitle();
if(!Strings.isNullOrEmpty(title) && !isChecked(view, "menuItem", title)){
String[] values = new String[]{
getModuleName(item, module),
className,
view,
"Toolbar MenuItem",
item.getName(),
exportService.translate(title, "en"),
exportService.translate(title, "fr"),
"",
""
};
exportService.writeRow(values, newForm, false, false);
newForm = false;
}
}
}