JNLP API позволяет неподписанному приложению запускаться в «песочнице» и в то же время получать безопасный доступ к локальным ресурсам. Например, в нем предусмотрены службы для загрузки и сохранения файлов. Приложения не видит файловой системы и не может специфицировать имена файлов.
Вместо этого отображается файловый диалог, и пользователь программы выбирает файл. Перед появлением диалогового окна для выбора файлов пользователь получает предупреждение и должен выразить согласие на продолжение работы. Более того, на самом деле API не предоставляет программе доступа к объекту File.
В частности, приложение не может самостоятельно обнаружить файл. Поэтому программистам предоставляются инструменты для реализации действий открытия и сохранения файла, но системная информация максимально скрыта от сомнительных приложений.API предоставляет разработчику описанные ниже возможности:
- Загрузка и сохранения файлов.
- Доступ к буферу обмена.
- Печать.
- Отображение документа в стандартном браузере.
- Хранение и извлечение конфигурационных данных.
- Средства, позволяющие убедиться в том, что выполняется только один экземпляр приложения(данная возможность была реализована в Java SE 5.0).
Чтобы воспользоваться этими возможностями, необходимо применить класс ServiceManager, примерно так:
1 |
FileSaveService service = (FileSaveService)ServiceManager.lookup("javax.jnlp.FileSaveService"); |
Если соответствующая функция оказалась недоступной, генерируется исключение UnavailableServiceException.
Для компиляции программы, использующей интерфейс JNLP API, необходимо указать в составе пути для поиска классов файл javaws.jar. Этот файл находится в подкаталоге jre/lib каталога JDK.
Теперь перейдем к обсуждению наиболее полезных возможностей JNLP. Чтобы сохранить файл, следует указать в диалоговом окне предложения о первоначальном пути и расширения файлов, а также сохраняемые данные и предполагаемое имя файла. Рассмотрим пример:
1 |
service.saveFileDialog(".", new String[] { "txt" }, data, "calc.txt"}; |
Данные должны быть доставлены в поток InputStream. Иногда эта задача оказывается довольно сложной. Программа, приведенная в конце статьи, использует описанную ниже стратегию.
- Создается экземпляр класса ByteArrayOutputStream, предназначенный для хранения байтов, подлежащих записи на диск.
- Создается экземпляр класса PrintStream, посылающий эти данные в поток ByteArrayOutputStream.
- Информация, подлежащая сохранению в потоке печати, выводится в PrintStream.
- Создается экземпляр класса ByteArrayInputStream, считывающий сохраненные байты.
- Поток передается методу saveFileDialog().
Более подробно потоки описываются в данной рубрике. Пока же мы можем не обращать внимания на детали, указанные в демонстрационной программе в конце урока.
Чтобы считать данные из файла, используется класс FileOpenService. Его метод openFileDialog() получает первоначальный путь и расширения файлов и возвращает объект класса FileContents. Затем можно вызвать метод getInputStream() и считать данные из файла. Если пользователь не выбрал файл, метод openFileDialog() возвращает значение null.
1 2 3 4 5 6 7 |
FileSaveService service = (FileSaveService)ServiceManager.lookup("javax.jnlp.FileSaveService"); FileContents contents = service.openFileDialog(".", new String[] { "txt" }); if(contents != null) { InputStream in = contents.getInputStream(); ... } |
Обратите внимание, что ваше приложение не знаете имени и места расположения файла. В противоположность этому, если вы хотите открыть определенный файл, то используете ExtendedService.
1 2 3 4 5 6 7 |
ExtendedService service = (ExtendedService)ServiceManager.lookup("javax.jnlp.ExtendedService"); FileContents contents = service.openFile(new File("C:\\autoexec.bat")); if(contents != null) { OutputStream out = contents.getOutputStream(); ... } |
Пользователь вашей программы должен разрешить доступ к файлу(смотрите рис.2).
Рис.2. Предупреждение о доступе к файлу
Для того чтобы отобразить документ в стандартном браузере(аналогично методу showDocument() для аплетов), применяется интерфейс BasicService. Учтите, что в некоторых системах стандартный браузер не установлен.
1 2 3 4 |
BasicService service = (BasicService)ServiceManager.lookup("javax.jnlp.BasicService"); if(service.isWebBrowserSupported()) service.showDocument(url); else... |
Существует устаревший метод PersistentService(), позволяющий приложению сохранять небольшие объемы информации о настройках и извлекать ее, если приложение запускается вновь. Эта возможность напоминает механизм cookie, применяемый при работе с протоколом HTTP. В качестве ключей используются URL. Эти URL не обязательно должны ссылаться на реальные ресурсы. Они лишь представляют собой удобную иерархическую схему именования файлов.
По каждому URL-ключу приложение может записать произвольные двоичные данные. Объем записи может быть ограничен и размером блока.
Для того чтобы изолировать приложение друг от друга, каждое приложение должно использовать URL, начинающейся с имени сервера, содержащего его код который указан в JNLP-файле. Например, если приложение загружено с Web-страницы https://pro-java.ru/apps, то оно может использовать лишь ключи, имеющие вид https://pro-java.ru/apps/subkey1/subkey2/… Попытка получить доступ к другим ключам будет заведомо неудачной.
Чтобы определить адрес сервера, на котором хранится код, приложение может вызвать метод getCodebase() класса BasicService.
Новый ключ создается с помощью метода create() класса PersistenceService:
1 2 |
Url url = new URL(codebase, "mykey"); servise.create(url, maxSize); |
Для доступа к информации, связанной с конкретным ключом, вызывается метод get(). Этот метод возвращает экземпляр класса FileContents, с помощью которого можно читать и записывать данные, соответствующие указанному ключу.
FileConents contents = service.get(url);
InputStream in = contents.getInputStream();
OutputStream out = contents.getOutputStream(true);
// true = перезапись
К сожалению, определить, существует указанный ключ или его нужно создать заново, довольно сложно. Мы может лишь надеяться, что ключ существует, и вызывать метод get. Если при этом генерируется исключение FileNotFoundException, необходимо создать новый ключ.
Начиная с Java SE 5.0, и приложение Java Web Start, и аплеты могут выводить данные на печать, используя обычные средства API. При этом отображается диалоговое окно, запрашивающее у пользователя согласие на доступ к принтеру.
Программа, код которой приведен ниже, представляет собой модификатор приложения, реализующего калькулятор. Этот калькулятор имеет виртуальную бумажную ленту, на которую записываются результаты всех вычислений. Список всех предыдущих вычислений можно сохранять и считывать из файла.
Для демонстрации постоянного хранения данных приложение позволяет задавать заголовок фрейма. Если программу запустить вновь, она извлечет этот заголовок из постоянного хранилища(смотрите рис.3).
Рис.3. Приложение WebStartCalculator
Вот исходный код программы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
import java.awt.EventQueue; import java.awt.event.*; import java.io.*; import java.net.*; import javax.swing.*; import javax.jnlp.*; /** * Калькулятор с поддержкой хронологии. Может * быть доставлен средствами Java Web Start. * @author pro-java.ru */ public class WebStartCalculator { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { CalculatorFrame frame = new CalculatorFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } /** * Фрейм, содержащий панелб калькулятора и меню *для чтения и записи предыдущих результатов. */ class CalculatorFrame extends JFrame { public CalculatorFrame() { setTitle(); panel = new CalculatorPanel(); add(panel); JMenu fileMenu = new JMenu("File"); JMenuItem openItem = fileMeni.add("Open"); openItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { open(); } }); JMenuItem saveItem = fileMenu.add("Save"); saveItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { save(); } }); JMenuBar menuBar = new JMenuBar(); menuBar.add(fileMenu); setJMenuBar(menuBar); pack(); } /** * Извлечение заголовка из постоянного хранилища. * Если ранее заголовок не был задан, он запращивается у пользователя. */ public void setTitle() { try { String title = null; BasicService basic = (BasicService) ServiceManager.lookup("javax.jnlp.BasicService"); URL codeBase = basic.getCodeBase(); PersistanceService service = (PersistanceService) ServiceManager.lookup("java.jnlp.PersistanceService"); URL key = new URL(codeBase, "title"); try { FileContents contents = service.get(key); InputStream in = contents.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(in)); title = reader.readLine(); } catch(FileNotFountException e) { title = JOptionPane.showInputDialog("Please suply a frame title:"); if(title == null) return; service.create(key, 100); FileContents contents = service.get(key); OutputStream out = contents.getOutputStream(true); PrintStream printOut = new PrintStream(out); printOut.print(title); } setTitle(title); } catch(UnavailableServiceException e) { JOptionPane.showMessageDialog(this, e); } catch(MalformedURLException e) { JOptionPane.showMessageDialog(this, e); } catch(IOException e) { JOptionPane.showMessageDialog(this, e); } } /** * Открытие файла с хронологией и обновление * отображаемых данных. */ public voi open() { try { FileOpenService service = (FileOpenService) ServiceManager.lookup("javax.jnlp.FileOpenService"); FileContents conents = service.openFileDialog(".", new String[] { "txt" }); JOptionPane.showMessageDialog(this, conents.getName()); if(contents != null) { InputStream in = contents.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(in)); String line; while((line = reader.readLine()) != null) { panel.append(line); panel.append("\n"); } } } catch(UnavailableServiceException e) { JOptionPane.showMessageDialog(this, e); } catch(IOException e) { JOptionPane.showMessageDialog(this, e); } } /** * Сохранение хронологии в файле. */ public void save() { try { ByteArrayOutputStream out = new ByteArrayOutputStream(); PrintStream printOut = new PrintStream(out); printOut.print(panel.getText()); InputStream data = new ByteArrayInputStream(out.toByteArray()); FileSaveService service = (FileSaveService) ServiceManager.lookup("javax.jnlp.FileSaveService"); service.saveFileDialog(".", new String[] { "txt" }, data, "calc.txt"); } catch(UnavailableServiceException e) { JOptionPane.showMessageDialog(this, e); } catch(IOException e) { JOptionPane.showMessageDialog(this, e); } } private CalculatorPanel panel; } |