Issue #3423 Move alarm/alert product requests off UI thread
Change-Id: I44149b5af54fbeac75231d6caeb3929fbf6ecf5e Former-commit-id:17b681f355
[formerly c3401cee78edae8dea6d21736fac25bfed996fb3] Former-commit-id:174d8b59e0
This commit is contained in:
parent
05e49db6b5
commit
64ba8b1c1a
4 changed files with 181 additions and 108 deletions
|
@ -62,6 +62,8 @@ import com.raytheon.viz.ui.dialogs.CaveSWTDialog;
|
|||
* Sep 6, 2012 13365 rferrel Accumulate and Display fix.
|
||||
* Sep 25, 2012 1196 lvenable Dialog refactor for AlarmDisplayWindow.Added DO_NOT_BLOCK.
|
||||
* Nov 20, 2013 2488 randerso Changed to use DejaVu font
|
||||
* Jul 24, 2014 3423 randerso Added setLoading() to indicate waiting on product
|
||||
* retrieval. Removed prods from constructor.
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
|
@ -100,19 +102,13 @@ public class AlarmDisplayWindow extends CaveSWTDialog {
|
|||
|
||||
/**
|
||||
* @param parentShell
|
||||
* @param prodList
|
||||
* @param accum_state
|
||||
*/
|
||||
protected AlarmDisplayWindow(Shell parentShell,
|
||||
java.util.List<StdTextProduct> prodList,
|
||||
ACCUMULATE_STATE accum_state) {
|
||||
protected AlarmDisplayWindow(Shell parentShell, ACCUMULATE_STATE accum_state) {
|
||||
super(parentShell, SWT.RESIZE, CAVE.PERSPECTIVE_INDEPENDENT
|
||||
| CAVE.INDEPENDENT_SHELL | CAVE.DO_NOT_BLOCK);
|
||||
setText("Alarm Display Window");
|
||||
prods = prodList;
|
||||
if (prods == null) {
|
||||
prods = new ArrayList<StdTextProduct>();
|
||||
}
|
||||
prods = new ArrayList<StdTextProduct>(0);
|
||||
this.accum_state = accum_state;
|
||||
}
|
||||
|
||||
|
@ -265,6 +261,11 @@ public class AlarmDisplayWindow extends CaveSWTDialog {
|
|||
populateText();
|
||||
}
|
||||
|
||||
public void setLoading() {
|
||||
actualText = text.getText();
|
||||
text.setText("Loading...");
|
||||
}
|
||||
|
||||
public void addText(String msg) {
|
||||
if (accumulate) {
|
||||
if (text.getText().isEmpty()) {
|
||||
|
|
|
@ -24,7 +24,13 @@ import java.util.ArrayList;
|
|||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
import org.eclipse.core.runtime.IStatus;
|
||||
import org.eclipse.core.runtime.Status;
|
||||
import org.eclipse.core.runtime.jobs.Job;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.events.ControlAdapter;
|
||||
import org.eclipse.swt.events.ControlEvent;
|
||||
|
@ -52,7 +58,9 @@ import com.raytheon.uf.common.dataplugin.text.db.StdTextProduct;
|
|||
import com.raytheon.uf.common.status.IUFStatusHandler;
|
||||
import com.raytheon.uf.common.status.UFStatus;
|
||||
import com.raytheon.uf.common.status.UFStatus.Priority;
|
||||
import com.raytheon.uf.common.util.Pair;
|
||||
import com.raytheon.uf.edex.services.textdbsrv.IQueryTransport;
|
||||
import com.raytheon.uf.viz.core.VizApp;
|
||||
import com.raytheon.viz.core.mode.CAVEMode;
|
||||
import com.raytheon.viz.texteditor.alarmalert.dialogs.AlarmDisplayWindow.ACCUMULATE_STATE;
|
||||
import com.raytheon.viz.texteditor.alarmalert.util.AlarmAlertFunctions;
|
||||
|
@ -97,8 +105,10 @@ import com.raytheon.viz.ui.dialogs.ModeListener;
|
|||
* Alarm Queue" GUI
|
||||
* Sep 6, 2012 13365 rferrel Accumulate and Display fix.
|
||||
* Sep 25, 2012 1196 lvenable Dialog refactor for AlarmDisplayWindow.
|
||||
* Mar 05,2013 15173 mgamazaychikov The dimensions and location of closed window
|
||||
* are saved and set on the next open.
|
||||
* Mar 05, 2013 15173 mgamazaychikov The dimensions and location of closed window
|
||||
* are saved and set on the next open.
|
||||
* Jul 24, 2014 3423 randerso Created eclipse job to get afos command
|
||||
* execution off the UI thread
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
|
@ -142,7 +152,7 @@ public class CurrentAlarmQueue extends CaveSWTDialog implements
|
|||
* Location and dimensions of the dialog on the close.
|
||||
*/
|
||||
private static Point closeLocation = null;
|
||||
|
||||
|
||||
private static Point closeDimensions = null;
|
||||
|
||||
/**
|
||||
|
@ -151,14 +161,65 @@ public class CurrentAlarmQueue extends CaveSWTDialog implements
|
|||
private boolean canRedraw = true;
|
||||
|
||||
/**
|
||||
* Maximum width,initial height and offset of the window
|
||||
* Maximum width,initial height and offset of the window
|
||||
*/
|
||||
private static final int SHELL_WIDTH = 350;
|
||||
|
||||
|
||||
private static final int INIT_HEIGHT = 200;
|
||||
|
||||
|
||||
private static final int INIT_OFFSET = 15;
|
||||
|
||||
/**
|
||||
* Job to retrieve text products off the UI thread
|
||||
*/
|
||||
private class ProduceTextProductsJob extends Job {
|
||||
private Queue<Pair<Long, String[]>> queue = new ConcurrentLinkedQueue<Pair<Long, String[]>>();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public ProduceTextProductsJob() {
|
||||
super("ProduceTextProductsJob");
|
||||
setSystem(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a text product retrieval job
|
||||
*
|
||||
* @param refTime
|
||||
* @param commands
|
||||
*/
|
||||
public void queue(Long refTime, String... commands) {
|
||||
queue.add(new Pair<Long, String[]>(refTime, commands));
|
||||
this.schedule();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IStatus run(IProgressMonitor monitor) {
|
||||
while (queue.size() > 0) {
|
||||
Pair<Long, String[]> pair = queue.poll();
|
||||
if (pair != null) {
|
||||
final java.util.List<StdTextProduct> prods = produceTextProducts(
|
||||
pair.getFirst(), pair.getSecond());
|
||||
|
||||
VizApp.runSync(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (alarmDisplayDlg != null
|
||||
&& !alarmDisplayDlg.isDisposed()) {
|
||||
alarmDisplayDlg.setProds(prods);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return Status.OK_STATUS;
|
||||
}
|
||||
}
|
||||
|
||||
private ProduceTextProductsJob produceTextProductsJob = new ProduceTextProductsJob();
|
||||
|
||||
/**
|
||||
* @param parentShell
|
||||
* @param style
|
||||
|
@ -261,12 +322,13 @@ public class CurrentAlarmQueue extends CaveSWTDialog implements
|
|||
shellComp = new Composite(shell, SWT.NONE);
|
||||
shellComp.setLayout(constructShellLayout());
|
||||
shellComp.setLayoutData(gd);
|
||||
|
||||
|
||||
/*
|
||||
* DR15173 - Create a listener to save the location
|
||||
* and dimensions of closed window.
|
||||
* DR15173 - Create a listener to save the location and dimensions of
|
||||
* closed window.
|
||||
*/
|
||||
shell.addShellListener(new ShellAdapter() {
|
||||
@Override
|
||||
public void shellClosed(ShellEvent event) {
|
||||
closeLocation = getShell().getLocation();
|
||||
closeDimensions = getShell().getSize();
|
||||
|
@ -289,11 +351,13 @@ public class CurrentAlarmQueue extends CaveSWTDialog implements
|
|||
Display.getDefault().asyncExec(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
/*
|
||||
* DR15173 - Enforce that the window width does not exceed the SHELL_WIDTH.
|
||||
*/
|
||||
shell.setBounds(location.x, location.y, SHELL_WIDTH, point.y);
|
||||
shell.setMinimumSize(SHELL_WIDTH, 0);
|
||||
/*
|
||||
* DR15173 - Enforce that the window width does not
|
||||
* exceed the SHELL_WIDTH.
|
||||
*/
|
||||
shell.setBounds(location.x, location.y, SHELL_WIDTH,
|
||||
point.y);
|
||||
shell.setMinimumSize(SHELL_WIDTH, 0);
|
||||
canRedraw = true;
|
||||
}
|
||||
});
|
||||
|
@ -302,7 +366,7 @@ public class CurrentAlarmQueue extends CaveSWTDialog implements
|
|||
|
||||
// Initialize all of the controls and layouts
|
||||
initializeComponents();
|
||||
|
||||
|
||||
// Set the shell location and dimensions.
|
||||
setShellGeometry();
|
||||
}
|
||||
|
@ -310,26 +374,25 @@ public class CurrentAlarmQueue extends CaveSWTDialog implements
|
|||
/**
|
||||
* Sets the geometry for the Current Alarm Queue shell
|
||||
*/
|
||||
private void setShellGeometry() {
|
||||
Rectangle displayArea = shell.getDisplay().getClientArea();
|
||||
int locationX = displayArea.x + INIT_OFFSET;
|
||||
int locationY = displayArea.y + INIT_OFFSET;
|
||||
int width = SHELL_WIDTH;
|
||||
int height = INIT_HEIGHT;
|
||||
if (CurrentAlarmQueue.closeLocation != null) {
|
||||
locationX = CurrentAlarmQueue.closeLocation.x;
|
||||
locationY = CurrentAlarmQueue.closeLocation.y;
|
||||
}
|
||||
if (CurrentAlarmQueue.closeDimensions != null) {
|
||||
height = CurrentAlarmQueue.closeDimensions.y;
|
||||
}
|
||||
shell.setMinimumSize(width, height);
|
||||
shell.setLocation(locationX, locationY);
|
||||
return;
|
||||
}
|
||||
private void setShellGeometry() {
|
||||
Rectangle displayArea = shell.getDisplay().getClientArea();
|
||||
int locationX = displayArea.x + INIT_OFFSET;
|
||||
int locationY = displayArea.y + INIT_OFFSET;
|
||||
int width = SHELL_WIDTH;
|
||||
int height = INIT_HEIGHT;
|
||||
if (CurrentAlarmQueue.closeLocation != null) {
|
||||
locationX = CurrentAlarmQueue.closeLocation.x;
|
||||
locationY = CurrentAlarmQueue.closeLocation.y;
|
||||
}
|
||||
if (CurrentAlarmQueue.closeDimensions != null) {
|
||||
height = CurrentAlarmQueue.closeDimensions.y;
|
||||
}
|
||||
shell.setMinimumSize(width, height);
|
||||
shell.setLocation(locationX, locationY);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
/**
|
||||
* Initializes each component of the shell
|
||||
*/
|
||||
private void initializeComponents() {
|
||||
|
@ -421,7 +484,7 @@ public class CurrentAlarmQueue extends CaveSWTDialog implements
|
|||
* Display the selected product the current alarm queue list.
|
||||
*/
|
||||
private void displayList() {
|
||||
String command = "";
|
||||
String command = null;
|
||||
Date refDate = null;
|
||||
if (list != null && list.getItemCount() > 0
|
||||
&& list.getSelectionCount() > 0 && list.getSelection() != null) {
|
||||
|
@ -436,14 +499,9 @@ public class CurrentAlarmQueue extends CaveSWTDialog implements
|
|||
}
|
||||
}
|
||||
|
||||
java.util.List<StdTextProduct> prods = null;
|
||||
if (command != "" && refDate != null) {
|
||||
prods = produceTextProduct(command, refDate.getTime());
|
||||
}
|
||||
|
||||
// Display the Alarm Display Window
|
||||
if (alarmDisplayDlg == null || alarmDisplayDlg.getShell().isDisposed()) {
|
||||
alarmDisplayDlg = new AlarmDisplayWindow(shell, prods,
|
||||
alarmDisplayDlg = new AlarmDisplayWindow(shell,
|
||||
ACCUMULATE_STATE.UNCHANGE);
|
||||
alarmDisplayDlg.setCloseCallback(new ICloseCallback() {
|
||||
|
||||
|
@ -458,58 +516,52 @@ public class CurrentAlarmQueue extends CaveSWTDialog implements
|
|||
|
||||
alarmDisplayDlg.open();
|
||||
} else {
|
||||
alarmDisplayDlg.setProds(prods);
|
||||
alarmDisplayDlg.setDialogFocus();
|
||||
}
|
||||
|
||||
if (command != null && refDate != null) {
|
||||
alarmDisplayDlg.setLoading();
|
||||
produceTextProductsJob.queue(refDate.getTime(), command);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display all the products in the alarm queue list and clear the list.
|
||||
*/
|
||||
private void displayAll() {
|
||||
String[] command = null;
|
||||
if (list != null) {
|
||||
command = new String[list.getItemCount()];
|
||||
for (int i = 0; i < list.getItemCount(); i++) {
|
||||
command[i] = list.getItems()[i].split(" ")[0];
|
||||
}
|
||||
// Do a count of how many instances of each command are in
|
||||
// the queue.
|
||||
Map<String, Integer> counter = new HashMap<String, Integer>();
|
||||
for (int i = 0; i < command.length; ++i) {
|
||||
if (counter.get(command[i]) == null) {
|
||||
counter.put(command[i], 0);
|
||||
} else {
|
||||
counter.put(command[i], (counter.get(command[i]) + 1));
|
||||
}
|
||||
}
|
||||
// For each command, see how far back it needs to go to
|
||||
// account for multiple instances, and construct the
|
||||
// appropriate AFOS command.
|
||||
for (int j = 0; j < command.length; ++j) {
|
||||
Integer count = counter.get(command[j]);
|
||||
if (count > 0) {
|
||||
String newCom = "-" + count.toString() + ":" + command[j];
|
||||
counter.put(command[j], (count - 1));
|
||||
command[j] = newCom;
|
||||
}
|
||||
}
|
||||
AlarmAlertLists.getInstance().getCurrentAlarms().clear();
|
||||
listDates.clear();
|
||||
list.removeAll();
|
||||
AlarmAlertFunctions.getAlarmalertbell().close();
|
||||
String[] command = new String[list.getItemCount()];
|
||||
for (int i = 0; i < list.getItemCount(); i++) {
|
||||
command[i] = list.getItems()[i].split(" ")[0];
|
||||
}
|
||||
|
||||
java.util.List<StdTextProduct> prods = new ArrayList<StdTextProduct>();
|
||||
if (command.length > 0) {
|
||||
for (int i = 0; i < command.length; i++) {
|
||||
prods.addAll(produceTextProduct(command[i]));
|
||||
// Do a count of how many instances of each command are in
|
||||
// the queue.
|
||||
Map<String, Integer> counter = new HashMap<String, Integer>();
|
||||
for (int i = 0; i < command.length; ++i) {
|
||||
if (counter.get(command[i]) == null) {
|
||||
counter.put(command[i], 0);
|
||||
} else {
|
||||
counter.put(command[i], (counter.get(command[i]) + 1));
|
||||
}
|
||||
}
|
||||
// For each command, see how far back it needs to go to
|
||||
// account for multiple instances, and construct the
|
||||
// appropriate AFOS command.
|
||||
for (int j = 0; j < command.length; ++j) {
|
||||
Integer count = counter.get(command[j]);
|
||||
if (count > 0) {
|
||||
String newCom = "-" + count.toString() + ":" + command[j];
|
||||
counter.put(command[j], (count - 1));
|
||||
command[j] = newCom;
|
||||
}
|
||||
}
|
||||
AlarmAlertLists.getInstance().getCurrentAlarms().clear();
|
||||
listDates.clear();
|
||||
list.removeAll();
|
||||
AlarmAlertFunctions.getAlarmalertbell().close();
|
||||
|
||||
// Display the Alarm Display Window
|
||||
if (alarmDisplayDlg == null || alarmDisplayDlg.getShell().isDisposed()) {
|
||||
alarmDisplayDlg = new AlarmDisplayWindow(shell, prods,
|
||||
alarmDisplayDlg = new AlarmDisplayWindow(shell,
|
||||
ACCUMULATE_STATE.TRUE);
|
||||
alarmDisplayDlg.setCloseCallback(new ICloseCallback() {
|
||||
@Override
|
||||
|
@ -523,10 +575,12 @@ public class CurrentAlarmQueue extends CaveSWTDialog implements
|
|||
});
|
||||
alarmDisplayDlg.open();
|
||||
} else {
|
||||
alarmDisplayDlg.setProds(prods);
|
||||
alarmDisplayDlg.setAccumulate(true);
|
||||
alarmDisplayDlg.setDialogFocus();
|
||||
}
|
||||
|
||||
alarmDisplayDlg.setLoading();
|
||||
produceTextProductsJob.queue(null, command);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -578,12 +632,6 @@ public class CurrentAlarmQueue extends CaveSWTDialog implements
|
|||
}
|
||||
}
|
||||
|
||||
public java.util.List<StdTextProduct> produceTextProduct(String command) {
|
||||
ICommand cmd = CommandFactory.getAfosCommand(command);
|
||||
executeCommand(cmd);
|
||||
return prodList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product for the given AFOS command and reference time.
|
||||
*
|
||||
|
@ -591,11 +639,16 @@ public class CurrentAlarmQueue extends CaveSWTDialog implements
|
|||
* @param refTime
|
||||
* @return prodList
|
||||
*/
|
||||
private java.util.List<StdTextProduct> produceTextProduct(String command,
|
||||
Long refTime) {
|
||||
ICommand cmd = CommandFactory.getAfosCommand(command, refTime);
|
||||
executeCommand(cmd);
|
||||
return prodList;
|
||||
private java.util.List<StdTextProduct> produceTextProducts(Long refTime,
|
||||
String... commands) {
|
||||
|
||||
java.util.List<StdTextProduct> prods = new ArrayList<StdTextProduct>();
|
||||
for (String command : commands) {
|
||||
ICommand cmd = CommandFactory.getAfosCommand(command, refTime);
|
||||
executeCommand(cmd);
|
||||
prods.addAll(prodList);
|
||||
}
|
||||
return prods;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -652,12 +705,19 @@ public class CurrentAlarmQueue extends CaveSWTDialog implements
|
|||
*/
|
||||
@Override
|
||||
public void currentAlarmChanged(CurrentAlarmEvent event) {
|
||||
AlarmAlertProduct aap = (AlarmAlertProduct) event.getSource();
|
||||
final AlarmAlertProduct aap = (AlarmAlertProduct) event.getSource();
|
||||
if (!shell.isDisposed() && shell != null) {
|
||||
CAVEMode mode = CAVEMode.getMode();
|
||||
if ((CAVEMode.OPERATIONAL.equals(mode) || CAVEMode.TEST
|
||||
.equals(mode)) && aap.getOperationalMode()) {
|
||||
addToQueue(aap.getProductId(), aap.getDateReceived());
|
||||
|
||||
VizApp.runAsync(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
addToQueue(aap.getProductId(), aap.getDateReceived());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,6 +56,7 @@ import com.raytheon.uf.common.localization.exception.LocalizationOpFailedExcepti
|
|||
import com.raytheon.uf.common.status.IUFStatusHandler;
|
||||
import com.raytheon.uf.common.status.UFStatus;
|
||||
import com.raytheon.uf.common.status.UFStatus.Priority;
|
||||
import com.raytheon.uf.viz.core.VizApp;
|
||||
import com.raytheon.uf.viz.core.localization.LocalizationManager;
|
||||
import com.raytheon.viz.texteditor.alarmalert.dialogs.AlarmAlertBell;
|
||||
import com.raytheon.viz.texteditor.command.CommandFactory;
|
||||
|
@ -79,6 +80,7 @@ import com.vividsolutions.jts.geom.GeometryFactory;
|
|||
* 03/19/2012 D. Friedman Fix determination of "Alarm" entries.
|
||||
* 12/07/2012 15555 m.gamazaychikov Added methods and constants for
|
||||
* the implementation of proximity alarm
|
||||
* 07/24/2014 3423 randerso Ensure ringBell is called on UI thread
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
|
@ -211,14 +213,16 @@ public class AlarmAlertFunctions {
|
|||
match = true;
|
||||
}
|
||||
if (match) {
|
||||
if (productFound == null)
|
||||
if (productFound == null) {
|
||||
productFound = p;
|
||||
}
|
||||
if ("Alarm".equals(p.getAlarmType()) && p.isAlarm()) {
|
||||
alarm = true;
|
||||
productFound = p;
|
||||
}
|
||||
if (alarm)
|
||||
if (alarm) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -229,7 +233,14 @@ public class AlarmAlertFunctions {
|
|||
instance.getCurrentAlarms().add(prod);
|
||||
instance.fireNewCurrentAlarmEvent(prod);
|
||||
|
||||
ringBell(alarm);
|
||||
final boolean sound = alarm;
|
||||
VizApp.runAsync(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
ringBell(sound);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,22 +23,22 @@ import com.raytheon.uf.common.dataplugin.text.alarms.AlarmAlertProduct;
|
|||
import com.raytheon.uf.common.status.IUFStatusHandler;
|
||||
import com.raytheon.uf.common.status.UFStatus;
|
||||
import com.raytheon.uf.common.status.UFStatus.Priority;
|
||||
import com.raytheon.uf.viz.core.VizApp;
|
||||
import com.raytheon.uf.viz.core.notification.INotificationObserver;
|
||||
import com.raytheon.uf.viz.core.notification.NotificationMessage;
|
||||
import com.raytheon.uf.viz.core.notification.jobs.NotificationManagerJob;
|
||||
import com.raytheon.viz.core.mode.CAVEMode;
|
||||
|
||||
/**
|
||||
* TODO Add Description
|
||||
* Alarm Alert Notification Observer
|
||||
*
|
||||
* <pre>
|
||||
*
|
||||
* SOFTWARE HISTORY
|
||||
* Date Ticket# Engineer Description
|
||||
* ------------ ---------- ----------- --------------------------
|
||||
* Oct 13, 2009 mnash Initial creation
|
||||
* Oct 13, 2009 mnash Initial creation
|
||||
* Jun 07, 2010 5851 cjeanbap Properly stop alarm/alert observer listener.
|
||||
* Jul 24, 2014 3423 randerso Get afos command execution off the UI thread
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
|
@ -108,7 +108,8 @@ public class AlarmAlertNotificationObserver implements INotificationObserver {
|
|||
}
|
||||
|
||||
if (run != null) {
|
||||
VizApp.runAsync(run);
|
||||
Thread thread = new Thread(run);
|
||||
thread.start();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
statusHandler.handle(Priority.PROBLEM,
|
||||
|
|
Loading…
Add table
Reference in a new issue