/* * Copyright 2013 Hewlett-Packard Development Company, L.P * * 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 com.hp.alm.ali.idea.entity.table; import com.hp.alm.ali.idea.entity.DummyStatusIndicator; import com.hp.alm.ali.idea.entity.EntityFilterModel; import com.hp.alm.ali.idea.entity.EntityQuery; import com.hp.alm.ali.idea.entity.EntityQueryProcessor; import com.hp.alm.ali.idea.entity.EntityRef; import com.hp.alm.ali.idea.entity.EntityStatusIndicator; import com.hp.alm.ali.idea.entity.FilterListener; import com.hp.alm.ali.idea.entity.queue.QueryTarget; import com.hp.alm.ali.idea.entity.queue.QueryQueue; import com.hp.alm.ali.idea.model.type.Context; import com.hp.alm.ali.idea.translate.Translator; import com.hp.alm.ali.idea.model.type.ContextAware; import com.hp.alm.ali.idea.services.EntityService; import com.hp.alm.ali.idea.model.Field; import com.hp.alm.ali.idea.entity.CachingEntityListener; import com.hp.alm.ali.idea.translate.TranslateService; import com.hp.alm.ali.idea.model.Metadata; import com.hp.alm.ali.idea.rest.NotConnectedException; import com.hp.alm.ali.idea.rest.ServerType; import com.hp.alm.ali.idea.rest.ServerTypeListener; import com.hp.alm.ali.idea.services.MetadataService; import com.hp.alm.ali.idea.model.Entity; import com.hp.alm.ali.idea.model.parser.EntityList; import com.hp.alm.ali.idea.rest.RestService; import com.intellij.openapi.Disposable; import com.intellij.openapi.project.Project; import com.intellij.util.ui.UIUtil; import javax.swing.*; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; public class EntityTableModel extends AbstractTableModel implements Disposable, MetadataService.DispatchMetadataCallback, CachingEntityListener, ServerTypeListener, QuerySharingManager.QuerySharing, EntityFilterModel<EntityQuery>,QueryTarget { private TableColumnModel columnModel; private boolean autoload; private RowSorter rowSorter; private RestService restService; private EntityStatusIndicator status; private Metadata meta; private String entityName; private Set<String> hiddenFields; private EntityQueryProcessor processor; private MetadataService metadataService; private EntityService entityService; private TranslateService translateService; private boolean ignoreSetOrder = false; private EntityMatcher matcher = new FieldBasedMatcher(); // instance obtained from AliConfiguration that is automatically persisted final private EntityQuery query; final private List<FilterListener> queryListeners = new LinkedList<FilterListener>(); final private List<Field> fields = new ArrayList<Field>(); private EntityList data = EntityList.empty(); private QueryQueue queue; private boolean forceQuery = false; private Map<Entity, Context> contextMap = new HashMap<Entity, Context>(); public EntityTableModel(Project project, TableColumnModel columnModel, boolean autoload, String entityName, EntityQuery query, MyRowSorter rowSorter, Set<String> hiddenFields, EntityQueryProcessor processor) { this.autoload = autoload; this.rowSorter = rowSorter; this.columnModel = columnModel; this.entityName = entityName; this.hiddenFields = hiddenFields; this.processor = processor; this.restService = project.getComponent(RestService.class); this.metadataService = project.getComponent(MetadataService.class); this.query = query; entityService = project.getComponent(EntityService.class); translateService = project.getComponent(TranslateService.class); status = new DummyStatusIndicator(); queue = new QueryQueue(project, status, true); entityService.addEntityListener(this); restService.addServerTypeListener(this); rowSorter.setModel(this); loadMetaData(); } private void loadMetaData() { try { status.loading(); metadataService.loadEntityMetadataAsync(entityName, this); } catch(NotConnectedException e) { status.info("Not connected", null, null, null); } } public void dispose() { } @Override public void connectedTo(final ServerType serverType) { UIUtil.invokeLaterIfNeeded(new Runnable() { @Override public void run() { if(!serverType.isConnected() || !autoload) { data = EntityList.empty(); meta = null; status.info("Disconnected", null, null, null); fireTableDataChanged(); } else { loadMetaData(); } } }); } public String getColumnName(int col) { return fields.get(col).getLabel(); } public Class getColumnClass(int col) { Field field = fields.get(col); if(translateService.isTranslated(field)) { return Translator.class; } else { return field.getClazz(); } } public int getRowCount() { return data.size(); } public int getColumnCount() { return fields.size(); } public boolean isCellEditable(int row, int col) { return false; } public void columnRemovedFromView(int col) { Field field = fields.get(col); query.removeView(field.getName()); fireFilterChangedEvent(); } public Object getValueAt(int row, int col) { String property = colToProperty(col); final Entity entity = data.get(row); Object value = entity.getProperty(property); if(value != null) { Class clazz = getColumnClass(col); if(Integer.class.equals(clazz)) { return Integer.valueOf(value.toString()); } } return value; } public Entity getEntity(int row) { return data.get(row); } public List<Field> getFields() { return Collections.unmodifiableList(fields); } public void setFilter(String prop, String value) { if(query.setValue(prop, value)) { fireFilterChangedEvent(); reload(); } } public void setOrder(List<RowSorter.SortKey> keys) { if(ignoreSetOrder) { return; } LinkedHashMap<String, SortOrder> map = new LinkedHashMap<String, SortOrder>(); for(RowSorter.SortKey key: keys) { String name = fields.get(key.getColumn()).getName(); map.put(name, key.getSortOrder()); } query.setOrder(map); fireFilterChangedEvent(); reload(); } public void fireTableStructureChanged() { LinkedHashMap<String, Integer> columns = query.getColumns(); while(columnModel.getColumnCount() > 0) { columnModel.removeColumn(columnModel.getColumn(0)); } for(String name: columns.keySet()) { Field field = meta.getAllFields().get(name); TableColumn column = new TableColumn(fields.indexOf(field), columns.get(name)); column.setHeaderValue(field.getLabel()); columnModel.addColumn(column); } List<RowSorter.SortKey> keys = new LinkedList<RowSorter.SortKey>(); for(Map.Entry<String, SortOrder> key: query.getOrder().entrySet()) { keys.add(new RowSorter.SortKey(fields.indexOf(meta.getAllFields().get(key.getKey())), key.getValue())); } List sortKeys = rowSorter.getSortKeys(); ignoreSetOrder = true; try { super.fireTableStructureChanged(); } finally { ignoreSetOrder = false; } if(!sortKeys.equals(keys)) { // restore visual sort indicator rowSorter.setSortKeys(keys); } else if(forceQuery) { reload(); } } public void updateColumnsFromView() { LinkedHashMap<String, Integer> map = new LinkedHashMap<String, Integer>(); for(int i = 0; i < columnModel.getColumnCount(); i++) { TableColumn column = columnModel.getColumn(i); map.put(fields.get(column.getModelIndex()).getName(), column.getWidth()); } query.setColumns(map); fireFilterChangedEvent(); } private String colToProperty(int col) { return fields.get(col).getName(); } public static List<String> quote(List<String> list) { if(list != null) { LinkedList<String> quoted = new LinkedList<String>(); List<String> ret = list; for(String value: list) { quoted.add("'"+value+"'"); if(value.contains(" ")) { // if at least one value needs quote, quote all to provide reasonable sort ret = quoted; } } return ret; } else { return null; } } public void reload() { if(meta == null) { loadMetaData(); } else { queryALM(query); } } private void queryALM(final EntityQuery query) { final EntityQuery clone; if(processor != null) { clone = processor.preProcess(query.clone()); } else { clone = query.clone(); } forceQuery = false; queue.query(clone, this); } public void setFilter(EntityQuery filter) { this.query.copyFrom(filter); if(meta != null) { this.query.purgeInvalid(meta); } fireFilterUpdated(true); } @Override public EntityQuery getFilter() { return query; } @Override public void fireFilterUpdated(boolean dataChanged) { if(dataChanged) { forceQuery = true; } fireFilterChangedEvent(); fireTableStructureChanged(); } public void metadataLoaded(Metadata metadata) { this.meta = metadata; // populate model fields.clear(); fields.addAll(meta.getAllFields().values()); // no columns specified, load defaults based on server type if(query.getColumns().isEmpty()) { query.setColumns(restService.getServerStrategy().getDefaultTableFilter(entityName).getColumns()); } // remove non-existing fields from filter query.purgeInvalid(meta); // restore columns into view UIUtil.invokeLaterIfNeeded(new Runnable() { public void run() { fireTableStructureChanged(); } }); reload(); } public void metadataFailed() { UIUtil.invokeLaterIfNeeded(new Runnable() { public void run() { status.info("Failed to load metadata", null, new Runnable() { public void run() { loadMetaData(); } }, null); } }); } @Override public void entityLoaded(final Entity entity, final Event event) { if(entityName.equals(entity.getType())) { UIUtil.invokeLaterIfNeeded(new Runnable() { public void run() { int i = data.indexOf(entity); if(i >= 0) { data.set(i, entity); fireTableRowsUpdated(i, i); } else if(event == Event.CREATE && matcher.matches(entity)) { data.add(entity); fireTableRowsInserted(data.size() - 1, data.size() - 1); } } }); } else if("release-backlog-item".equals(entity.getType()) && entityName.equals(entity.getPropertyValue("entity-type"))) { UIUtil.invokeLaterIfNeeded(new Runnable() { public void run() { Entity workItem = new Entity(entity.getPropertyValue("entity-type"), Integer.valueOf(entity.getPropertyValue("entity-id"))); int i = data.indexOf(workItem); if(i >= 0) { data.get(i).mergeRelatedEntity(entity); fireTableRowsUpdated(i, i); } } }); } } @Override public void entityNotFound(final EntityRef ref, boolean removed) { UIUtil.invokeLaterIfNeeded(new Runnable() { public void run() { int i = data.indexOf(ref.toEntity()); if(i >= 0) { Entity entity = data.remove(i); contextMap.remove(entity); fireTableRowsDeleted(i, i); } else if(entityName.equals("release-backlog-item")) { for(int j = 0; j < data.size(); j++) { Entity entity = data.get(j); if(entity.getPropertyValue("entity-type").equals(ref.type) && entity.getPropertyValue("entity-id").equals(String.valueOf(ref.id))) { Entity entity1 = data.remove(i); contextMap.remove(entity1); fireTableRowsDeleted(j, j); break; } } } } }); } public Entity lookup(final EntityRef ref) { final LinkedList<Entity> ret = new LinkedList<Entity>(); UIUtil.invokeAndWaitIfNeeded(new Runnable() { public void run() { int i = data.indexOf(new Entity(ref.type, ref.id)); if(i >= 0) { ret.add(data.get(i)); } } }); if(ret.isEmpty()) { return null; } else { return ret.get(0); } } @Override public void addFilterListener(FilterListener listener) { synchronized (queryListeners) { queryListeners.add(listener); } } public void fireFilterChangedEvent() { synchronized (queryListeners) { for(FilterListener listener: queryListeners) { listener.filterChanged(query); } } } public void setStatusIndicator(EntityStatusIndicator status) { this.status = status; queue.setStatusIndicator(status); } public int indexOf(Entity entity) { return data.indexOf(entity); } @Override public void setColumns(LinkedHashMap<String, Integer> columns) { HashSet<String> newColumns = new HashSet<String>(columns.keySet()); newColumns.removeAll(query.getColumns().keySet()); if (!newColumns.isEmpty()) { // adding new column into query, must reload forceQuery = true; } query.setColumns(columns); fireFilterChangedEvent(); fireTableStructureChanged(); } public Set<String> getHiddenFields() { return hiddenFields; } public Translator getTranslator(Field field, int row, Entity masterEntity) { Translator translator = translateService.getTranslator(field); if(translator instanceof ContextAware) { Entity entity = getEntity(row); Context context = contextMap.get(entity); if(context == null) { context = new Context(entity); context.setMasterEntity(masterEntity); contextMap.put(entity, context); } ((ContextAware) translator).setContext(context); } return translator; } public void setMatcher(EntityMatcher matcher) { this.matcher = matcher; } @Override public void handleResult(final EntityList list) { UIUtil.invokeLaterIfNeeded(new Runnable() { public void run() { data = list; fireTableDataChanged(); } }); } public interface EntityMatcher { boolean matches(Entity entity); } public class FieldBasedMatcher implements EntityMatcher { @Override public boolean matches(Entity entity) { for(String field: hiddenFields) { if(field.startsWith("virtual:")) { continue; } if(!entity.getPropertyValue(field).equals(getFilter().getValue(field))) { return false; } } return true; } } }