/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2017 Serge Rider (serge@jkiss.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.jkiss.dbeaver.runtime.properties;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBPContextProvider;
import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor;
import org.jkiss.dbeaver.model.preferences.DBPPropertyManager;
import org.jkiss.dbeaver.model.preferences.DBPPropertySource;
import org.jkiss.dbeaver.model.exec.DBCExecutionContext;
import org.jkiss.dbeaver.model.impl.PropertyDescriptor;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.struct.DBSObject;
import org.jkiss.dbeaver.model.runtime.load.AbstractLoadService;
import org.jkiss.dbeaver.model.runtime.load.ILoadVisualizer;
import org.jkiss.dbeaver.ui.LoadingJob;
import org.jkiss.utils.ArrayUtils;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
/**
* PropertyCollector
*/
public abstract class PropertySourceAbstract implements DBPPropertyManager, IPropertySourceMulti
{
private static final Log log = Log.getLog(PropertySourceAbstract.class);
private Object sourceObject;
private Object object;
private boolean loadLazyProps;
private final List<DBPPropertyDescriptor> props = new ArrayList<>();
private final Map<Object, Object> propValues = new HashMap<>();
private final Map<Object, Object> lazyValues = new HashMap<>();
private final List<ObjectPropertyDescriptor> lazyProps = new ArrayList<>();
private LoadingJob<Map<ObjectPropertyDescriptor, Object>> lazyLoadJob;
/**
* constructs property source
* @param object object
*/
public PropertySourceAbstract(Object sourceObject, Object object, boolean loadLazyProps)
{
this.sourceObject = sourceObject;
this.object = object;
this.loadLazyProps = loadLazyProps;
}
public synchronized void addProperty(DBPPropertyDescriptor prop)
{
if (prop instanceof ObjectPropertyDescriptor && ((ObjectPropertyDescriptor) prop).isHidden()) {
// Do not add it to property list
} else {
props.add(prop);
}
propValues.put(prop.getId(), prop);
}
public synchronized void addProperty(@Nullable String category, Object id, String name, Object value)
{
props.add(new PropertyDescriptor(category, id, name, null, value == null ? null : value.getClass(), false, null, null, false));
propValues.put(id, value);
}
public synchronized void removeProperty(DBPPropertyDescriptor prop)
{
propValues.remove(prop.getId());
lazyValues.remove(prop.getId());
props.remove(prop);
}
public synchronized void clearProperties()
{
props.clear();
propValues.clear();
lazyValues.clear();
}
public synchronized boolean hasProperty(ObjectPropertyDescriptor prop)
{
return props.contains(prop);
}
public synchronized boolean isEmpty()
{
return props.isEmpty();
}
public DBPPropertyDescriptor[] getProperties() {
return props.toArray(new DBPPropertyDescriptor[props.size()]);
}
public DBPPropertyDescriptor getProperty(String id) {
for (DBPPropertyDescriptor prop : props) {
if (prop.getId().equals(id)) {
return prop;
}
}
return null;
}
@Override
public boolean isDirty(Object id) {
return false;
}
public Object getSourceObject()
{
return sourceObject;
}
@Override
public Object getEditableValue()
{
return object;
}
@Override
public DBPPropertyDescriptor[] getPropertyDescriptors2() {
return props.toArray(new DBPPropertyDescriptor[props.size()]);
}
/*
public IPropertyDescriptor getPropertyDescriptor(final Object id)
{
for (IPropertyDescriptor prop : props) {
if (CommonUtils.equalObjects(prop.getId(), id)) {
return prop;
}
}
return null;
}
*/
@Override
public boolean isPropertySet(Object id)
{
Object value = propValues.get(id);
if (value instanceof ObjectPropertyDescriptor) {
return isPropertySet(getEditableValue(), (ObjectPropertyDescriptor) value);
} else {
return value != null;
}
}
@Override
public boolean isPropertySet(Object object, ObjectPropertyDescriptor prop)
{
try {
return !prop.isLazy(object, true) && prop.readValue(object, null) != null;
} catch (Exception e) {
log.error("Error reading property '" + prop.getId() + "' from " + object, e);
return false;
}
}
@Override
public final Object getPropertyValue(@Nullable DBRProgressMonitor monitor, final Object id)
{
Object value = propValues.get(id);
if (value instanceof ObjectPropertyDescriptor) {
value = getPropertyValue(monitor, getEditableValue(), (ObjectPropertyDescriptor) value);
}
return value;
}
@Override
public Object getPropertyValue(@Nullable DBRProgressMonitor monitor, final Object object, final ObjectPropertyDescriptor prop)
{
try {
if (monitor == null && prop.isLazy(object, true) && !prop.supportsPreview()) {
final Object value = lazyValues.get(prop.getId());
if (value != null) {
return value;
}
if (lazyValues.containsKey(prop.getId())) {
// Some lazy props has null value
return null;
}
if (!loadLazyProps) {
return null;
} else {
synchronized (lazyProps) {
lazyProps.add(prop);
if (lazyLoadJob == null) {
// We assume that it can be called ONLY by properties viewer
// So, start lazy loading job to update it after value will be loaded
lazyLoadJob = LoadingJob.createService(
new PropertySheetLoadService(),
new PropertySheetLoadVisualizer());
lazyLoadJob.addJobChangeListener(new JobChangeAdapter() {
@Override
public void done(IJobChangeEvent event)
{
synchronized (lazyProps) {
if (!lazyProps.isEmpty()) {
lazyLoadJob.schedule(100);
} else {
lazyLoadJob = null;
}
}
}
});
lazyLoadJob.schedule(100);
}
}
// Return dummy string for now
lazyValues.put(prop.getId(), null);
return null;
}
} else {
return prop.readValue(object, monitor);
}
} catch (Throwable e) {
if (e instanceof InvocationTargetException) {
e = ((InvocationTargetException) e).getTargetException();
}
log.error("Error reading property '" + prop.getId() + "' from " + object, e);
return e.getMessage();
}
}
@Override
public boolean isPropertyResettable(Object id)
{
Object value = propValues.get(id);
if (value instanceof ObjectPropertyDescriptor) {
return isPropertyResettable(getEditableValue(), (ObjectPropertyDescriptor) value);
} else {
// No by default
return false;
}
}
@Override
public boolean isPropertyResettable(Object object, ObjectPropertyDescriptor prop)
{
return false;
}
@Override
public final void resetPropertyValue(@Nullable DBRProgressMonitor monitor, Object id)
{
Object value = propValues.get(id);
if (value instanceof ObjectPropertyDescriptor) {
resetPropertyValue(monitor, getEditableValue(), (ObjectPropertyDescriptor) value);
} else {
throw new UnsupportedOperationException("Direct property reset not implemented");
}
}
@Override
public void resetPropertyValue(@Nullable DBRProgressMonitor monitor, Object object, ObjectPropertyDescriptor id)
{
throw new UnsupportedOperationException("Cannot reset property in non-editable property source");
}
@Override
public void resetPropertyValueToDefault(Object id) {
throw new UnsupportedOperationException("Cannot reset property in non-editable property source");
}
@Override
public final void setPropertyValue(@Nullable DBRProgressMonitor monitor, Object id, Object value)
{
Object prop = propValues.get(id);
if (prop instanceof ObjectPropertyDescriptor) {
setPropertyValue(monitor, getEditableValue(), (ObjectPropertyDescriptor) prop, value);
lazyValues.put(((ObjectPropertyDescriptor) prop).getId(), value);
} else {
propValues.put(id, value);
}
}
@Override
public void setPropertyValue(@Nullable DBRProgressMonitor monitor, Object object, ObjectPropertyDescriptor prop, Object value)
{
throw new UnsupportedOperationException("Cannot update property in non-editable property source");
}
public boolean collectProperties()
{
lazyValues.clear();
props.clear();
propValues.clear();
final Object editableValue = getEditableValue();
IPropertyFilter filter;
if (editableValue instanceof DBSObject) {
filter = new DataSourcePropertyFilter(((DBSObject) editableValue).getDataSource());
} else if (editableValue instanceof DBPContextProvider) {
DBCExecutionContext context = ((DBPContextProvider) editableValue).getExecutionContext();
filter = context == null ? new DataSourcePropertyFilter() : new DataSourcePropertyFilter(context.getDataSource());
} else {
filter = new DataSourcePropertyFilter();
}
List<ObjectPropertyDescriptor> annoProps = ObjectAttributeDescriptor.extractAnnotations(this, editableValue.getClass(), filter);
for (final ObjectPropertyDescriptor desc : annoProps) {
addProperty(desc);
}
if (editableValue instanceof DBPPropertySource) {
DBPPropertySource ownPropSource = (DBPPropertySource) editableValue;
DBPPropertyDescriptor[] ownProperties = ownPropSource.getPropertyDescriptors2();
if (!ArrayUtils.isEmpty(ownProperties)) {
for (DBPPropertyDescriptor prop : ownProperties) {
props.add(prop);
propValues.put(prop.getId(), ownPropSource.getPropertyValue(null, prop.getId()));
}
}
}
return !props.isEmpty();
}
private class PropertySheetLoadService extends AbstractLoadService<Map<ObjectPropertyDescriptor, Object>> {
public static final String TEXT_LOADING = "...";
public PropertySheetLoadService()
{
super(TEXT_LOADING);
}
@Override
public Map<ObjectPropertyDescriptor, Object> evaluate(DBRProgressMonitor monitor)
throws InvocationTargetException, InterruptedException
{
try {
Map<ObjectPropertyDescriptor, Object> result = new IdentityHashMap<>();
for (ObjectPropertyDescriptor prop : obtainLazyProperties()) {
if (monitor.isCanceled()) {
break;
}
result.put(prop, prop.readValue(getEditableValue(), monitor));
}
return result;
} catch (Throwable ex) {
if (ex instanceof InvocationTargetException) {
throw (InvocationTargetException)ex;
} else {
throw new InvocationTargetException(ex);
}
}
}
@Override
public Object getFamily() {
final Object editableValue = getEditableValue();
return editableValue instanceof DBSObject ? ((DBSObject) editableValue).getDataSource() : editableValue;
}
}
private List<ObjectPropertyDescriptor> obtainLazyProperties()
{
synchronized (lazyProps) {
if (lazyProps.isEmpty()) {
return Collections.emptyList();
} else {
List<ObjectPropertyDescriptor> result = new ArrayList<>(lazyProps);
lazyProps.clear();
return result;
}
}
}
private class PropertySheetLoadVisualizer implements ILoadVisualizer<Map<ObjectPropertyDescriptor, Object>> {
//private Object propertyId;
//private int callCount = 0;
private boolean completed = false;
private PropertySheetLoadVisualizer()
{
}
@Override
public DBRProgressMonitor overwriteMonitor(DBRProgressMonitor monitor)
{
return monitor;
}
@Override
public boolean isCompleted()
{
return completed;
}
@Override
public void visualizeLoading()
{
/*
String dots;
switch (callCount++ % 4) {
case 0: dots = ""; break;
case 1: dots = "."; break;
case 2: dots = ".."; break;
case 3: default: dots = "..."; break;
}
propValues.put(propertyId, PropertySheetLoadService.TEXT_LOADING + dots);
refreshProperties(false);
*/
}
@Override
public void completeLoading(Map<ObjectPropertyDescriptor, Object> result)
{
completed = true;
if (result != null) {
for (Map.Entry<ObjectPropertyDescriptor, Object> entry : result.entrySet()) {
lazyValues.put(entry.getKey().getId(), entry.getValue());
PropertiesContributor.getInstance().notifyPropertyLoad(getEditableValue(), entry.getKey(), entry.getValue(), true);
}
}
}
}
}