blob: 22f8c7c467bcf51a447872b25e2662f016680655 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.netbeans.modules.derby;
import java.awt.EventQueue;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.Preferences;
import javax.swing.Icon;
import org.netbeans.api.db.explorer.ConnectionManager;
import org.netbeans.api.db.explorer.DatabaseConnection;
import org.netbeans.api.db.explorer.DatabaseException;
import org.netbeans.api.db.explorer.JDBCDriver;
import org.netbeans.api.db.explorer.JDBCDriverManager;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.netbeans.modules.derby.api.DerbyDatabases;
import org.netbeans.modules.derby.ui.SecurityManagerBugPanel;
import org.netbeans.spi.db.explorer.DatabaseRuntime;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.awt.NotificationDisplayer;
import org.openide.execution.NbProcessDescriptor;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.util.Cancellable;
import org.openide.util.ImageUtilities;
import org.openide.util.NbBundle;
import org.openide.util.NbPreferences;
import org.openide.util.RequestProcessor;
import org.openide.util.Utilities;
import org.openide.windows.InputOutput;
/**
*
* @author Ludo, Petr Jiricka
*/
public class RegisterDerby implements DatabaseRuntime {
// XXX this class does too much. Should maybe be split into
// DatabaseRuntimeImpl, DerbyStartStop and the rest.
// XXX refactor this soon, it is full of race conditions!
private static final Logger LOGGER = Logger.getLogger(RegisterDerby.class.getName());
private static final boolean LOG = LOGGER.isLoggable(Level.FINE);
private static final String DISABLE_SECURITY_MANAGER
= "disableSecurityManager"; //NOI18N
private static final String DO_NOT_CHECK_SECURITY_MANAGER_BUG
= "doNotCheckSecurityManagerBug"; //NOI18N
private static final String SECURITY_MANAGER_BUG_OUTPUT
= "java.security.AccessControlException: " //NOI18N
+ "access denied (\"java.net.SocketPermission\""; //NOI18N
private static final int START_TIMEOUT = 0; // seconds
private static RegisterDerby reg=null;
/** Derby server process */
private static Process process = null;
/** Creates a new instance of RegisterDerby */
private RegisterDerby() {}
public static synchronized RegisterDerby getDefault(){
if (reg==null) {
reg= new RegisterDerby();
if (EventQueue.isDispatchThread()) { // #229741
RequestProcessor.getDefault().post(new Runnable() {
@Override
public void run() {
DerbyActivator.activate();
}
});
} else {
DerbyActivator.activate();
}
}
return reg;
}
/**
* Whether this runtime accepts this connection string.
*/
@Override
public boolean acceptsDatabaseURL(String url){
return url.trim().startsWith("jdbc:derby://localhost"); // NOI18N
}
/**
* Is database server up and running.
*/
@Override
public boolean isRunning(){
if (process!=null){
try{
int e = process.exitValue();
process=null;
} catch (IllegalThreadStateException e){
//not exited yet...it's ok
}
}
return (process!=null);
}
@Override
public String getJDBCDriverClass() {
return DerbyOptions.DRIVER_CLASS_NET;
}
/**
* Can the database be started from inside the IDE?
*/
@Override
public boolean canStart(){
// issue 81619: should only try to start if the location is set
return DerbyOptions.getDefault().getLocation().length() > 0;
}
/**
* Start the database server.
*/
@Override
public void start(){
start(START_TIMEOUT);
}
private String getNetworkServerClasspath() {
return
Util.getDerbyFile("lib/derby.jar").getAbsolutePath() + File.pathSeparator +
Util.getDerbyFile("lib/derbytools.jar").getAbsolutePath() + File.pathSeparator +
Util.getDerbyFile("lib/derbynet.jar").getAbsolutePath(); // NOI18N
}
/**
* Returns the registered Derby driver.
*/
private JDBCDriver getRegisteredDerbyDriver() {
JDBCDriver[] drvs = JDBCDriverManager.getDefault().getDrivers(DerbyOptions.DRIVER_CLASS_NET);
if (drvs.length > 0) {
return drvs[0];
}
return null;
}
public int getPort() {
return 1527;
}
/** Posts the creation of the new database to request processor.
*/
void postCreateNewDatabase(final String databaseName, final String user, final String password) throws Exception {
RequestProcessor.getDefault().post(new Runnable() {
@Override
public void run () {
try {
// DerbyDatabases.createDatabase would start the database too, but
// doing it beforehand to avoid having two progress bars running
if (!ensureStarted(true)) {
return;
}
ProgressHandle ph = ProgressHandleFactory.createHandle(NbBundle.getMessage(
RegisterDerby.class, "MSG_CreatingDBProgressLabel", databaseName));
ph.start();
try {
DerbyDatabases.createDatabase(databaseName, user, password);
} finally {
ph.finish();
}
} catch (RuntimeException | DatabaseException | IOException e) {
LOGGER.log(Level.WARNING, null, e);
String message = NbBundle.getMessage(RegisterDerby.class, "ERR_CreateDatabase", e.getMessage());
Util.showInformation(message);
}
}
});
}
private String getDerbySystemHome() {
// return System.getProperty("netbeans.user") + File.separator + "derby";
return DerbyOptions.getDefault().getSystemHome();
}
private void createDerbyPropertiesFile() {
File derbyProperties = new File(getDerbySystemHome(), "derby.properties");
if (derbyProperties.exists()) {
return;
}
Properties derbyProps = new Properties();
// fill it
if (Utilities.isMac()) {
derbyProps.setProperty("derby.storage.fileSyncTransactionLog", "true");
}
// write it out
OutputStream fileos = null;
try {
File derbyPropertiesParent = derbyProperties.getParentFile();
derbyPropertiesParent.mkdirs();
fileos = new FileOutputStream(derbyProperties);
derbyProps.store(fileos, NbBundle.getMessage(RegisterDerby.class, "MSG_DerbyPropsFile"));
} catch (IOException ex) {
LOGGER.log(Level.INFO, ex.getLocalizedMessage() + " while createDerbyPropertiesFile into " + derbyProps, ex);
} finally {
if (fileos != null) {
try {
fileos.close();
} catch (IOException ex) {
LOGGER.log(Level.WARNING, null, ex);
}
}
}
}
private File getInstallLocation() {
String location = DerbyOptions.getDefault().getLocation();
if (location.equals("")) { // NOI18N
return null;
}
return new File(location);
}
private String[] getEnvironment() {
String location = DerbyOptions.getDefault().getLocation();
if (location.equals("")) { // NOI18N
return null;
}
return new String[] { "DERBY_INSTALL=" + location }; // NOI18N
}
/**
* Start the database server, and wait some time (in milliseconds) to make sure the server is active.
*
* @param waitTime the time to wait. If less than or equal to zero, do not
* wait at all.
*
* @return true if the server is definitely started, false otherwise (the server is
* not started or the status is unknown). If <code>waitTime</code> was
* less than zero, then always false.
*/
private boolean start(int waitTime){
if (process!=null){// seems to be already running?
stop();
}
if (!Util.checkInstallLocation()) {
return false;
}
try {
String java = getJavaExecutable();
// create the derby.properties file
createDerbyPropertiesFile();
// java -Dderby.system.home="<userdir/derby>" -classpath
// "<DERBY_INSTALL>/lib/derby.jar:<DERBY_INSTALL>/lib/derbytools.jar:<DERBY_INSTALL>/lib/derbynet.jar"
// org.apache.derby.drda.NetworkServerControl start
NbProcessDescriptor desc = new NbProcessDescriptor(
java,
"-Dderby.system.home=\"" + getDerbySystemHome() + "\" " +
"-classpath \"" + getNetworkServerClasspath() + "\"" +
" org.apache.derby.drda.NetworkServerControl start" + startArgs()
);
if (LOG) {
LOGGER.log(Level.FINE, "Running {0} {1}", new Object[]{desc.getProcessName(), desc.getArguments()});
}
process = desc.exec (
null,
getEnvironment(),
true,
getInstallLocation()
);
ExecSupport ee = new ExecSupport(process,NbBundle.getMessage(StartAction.class, "LBL_outputtab"));
ee.setStringToLookFor("" + getPort()); // NOI18N
addSecurityBugHandler(ee);
ee.start();
if (waitTime >= 0) {
// to make sure the server is up and running
boolean canStart = waitStart(ee, waitTime);
if (!canStart) {
stop();
}
return canStart;
} else {
return false;
}
} catch (IOException | RuntimeException e) {
Util.showInformation(e.getLocalizedMessage());
return false;
} finally {
InputOutput io = org.openide.windows.IOProvider.getDefault().getIO(
NbBundle.getMessage(StartAction.class, "LBL_outputtab"), false);
io.getOut().close();
}
}
/**
* Add security manager bug handler, if needed. See bug #239962.
*/
private void addSecurityBugHandler(ExecSupport ee) {
Preferences prefs = NbPreferences.forModule(RegisterDerby.class);
if (!prefs.getBoolean(DISABLE_SECURITY_MANAGER, false)
&& !prefs.getBoolean(DO_NOT_CHECK_SECURITY_MANAGER_BUG, false)) {
ee.addOutputStringHandler(SECURITY_MANAGER_BUG_OUTPUT,
createSecurityManagerBugHandler());
}
}
@NbBundle.Messages({
"TTL_SecurityBug=JavaDB - Security Manager Problem"
})
public Runnable createSecurityManagerBugHandler() {
return new Runnable() {
@Override
public void run() {
if (!EventQueue.isDispatchThread()) {
EventQueue.invokeLater(this);
return;
}
class NotifyPanel extends SecurityManagerBugPanel {
@Override
public void disableSecurityManagerClicked() {
NbPreferences.forModule(RegisterDerby.class).putBoolean(
DISABLE_SECURITY_MANAGER, true);
}
@Override
public void doNotShowAgainClicked() {
NbPreferences.forModule(RegisterDerby.class).putBoolean(
DO_NOT_CHECK_SECURITY_MANAGER_BUG, true);
}
}
NotificationDisplayer.getDefault().notify(
Bundle.TTL_SecurityBug(), getDbIcon(),
new NotifyPanel(), new NotifyPanel(),
NotificationDisplayer.Priority.HIGH);
}
};
}
/**
* Check whether some non-standard arguments are needed to start JavaDB.
*
* @return String containing non-standard arguments, prefixed by a space
* character (if not empty).
*/
private String startArgs() {
Preferences prefs = NbPreferences.forModule(RegisterDerby.class);
if (prefs.getBoolean(DISABLE_SECURITY_MANAGER, false)) {
return " -noSecurityManager"; //NOI18N
} else {
return ""; //NOI18N
}
}
private Icon getDbIcon() {
return ImageUtilities.loadImageIcon(
"org/netbeans/modules/derby/resources/database.gif", //NOI18N
false);
}
private boolean waitStart(final ExecSupport execSupport, int waitTime) {
boolean started = false;
final boolean[] forceExit = new boolean[1];
String waitMessage = NbBundle.getMessage(RegisterDerby.class, "MSG_StartingDerby");
ProgressHandle progress = ProgressHandleFactory.createHandle(waitMessage, new Cancellable() {
@Override
public boolean cancel() {
forceExit[0] = true;
return execSupport.interruptWaiting();
}
});
progress.start();
try {
while (!started) {
started = execSupport.waitForMessage(waitTime * 1000);
if (!started) {
if (waitTime > 0 && (!forceExit[0])) {
String title = NbBundle.getMessage(RegisterDerby.class, "LBL_DerbyDatabase");
String message = NbBundle.getMessage(RegisterDerby.class, "MSG_WaitStart", waitTime);
NotifyDescriptor waitConfirmation = new NotifyDescriptor.Confirmation(message, title, NotifyDescriptor.YES_NO_OPTION);
if (DialogDisplayer.getDefault().notify(waitConfirmation)
!= NotifyDescriptor.YES_OPTION) {
break;
}
} else {
break;
}
}
}
if (!started) {
execSupport.terminate();
LOGGER.log(Level.WARNING, "Derby server failed to start"); // NOI18N
}
} finally {
progress.finish();
}
return started;
}
/**
* Stop the database server.
*/
@Override
public void stop(){
try {
if (process==null){//nothing to stop...
return;
}
String java = getJavaExecutable();
// java -Dderby.system.home="<userdir/derby>" -classpath
// "<DERBY_INSTALL>/lib/derby.jar:<DERBY_INSTALL>/lib/derbytools.jar:<DERBY_INSTALL>/lib/derbynet.jar"
// org.apache.derby.drda.NetworkServerControl shutdown
NbProcessDescriptor desc = new NbProcessDescriptor(
java,
"-Dderby.system.home=\"" + getDerbySystemHome() + "\" " +
"-classpath \"" + getNetworkServerClasspath() + "\"" +
" org.apache.derby.drda.NetworkServerControl shutdown"
);
if (LOG) {
LOGGER.log(Level.FINE, "Running {0} {1}", new Object[]{desc.getProcessName(), desc.getArguments()});
}
Process shutwownProcess = desc.exec (
null,
getEnvironment(),
true,
getInstallLocation()
);
shutwownProcess.waitFor();
process.destroy();
disconnectAllDerbyConnections();
} catch (IOException | InterruptedException | RuntimeException e) {
Util.showInformation(e.getMessage());
}
finally {
process=null;
}
}
private static String getJavaExecutable() {
File javaExe = new File(System.getProperty("java.home"), "/bin/java" + (Utilities.isWindows() ? ".exe" : "")); // NOI18N
assert javaExe.exists() && javaExe.canExecute() : javaExe + " exists and it's executable.";
File javaExeNormalized = FileUtil.normalizeFile(javaExe);
FileObject javaFO = FileUtil.toFileObject(javaExeNormalized);
if (javaFO == null) {
throw new RuntimeException (NbBundle.getMessage(RegisterDerby.class, "EXC_JavaExecutableNotFound"));
}
String java = FileUtil.toFile(javaFO).getAbsolutePath();
if (java == null) {
throw new RuntimeException (NbBundle.getMessage(RegisterDerby.class, "EXC_JavaExecutableNotFound"));
}
return java;
}
private void disconnectAllDerbyConnections() {
DatabaseConnection[] dbconn = ConnectionManager.getDefault().getConnections();
for (int i = 0; i < dbconn.length; i++) {
if (RegisterDerby.getDefault().acceptsDatabaseURL(dbconn[i].getDatabaseURL())) {
ConnectionManager.getDefault().disconnect(dbconn[i]);
}
}
}
/**
* Starts the server if necessary, and can wait for it to start if it was
* not already started.
*
* @param waitIfNotStarted true if to wait for a certain period of time for the server to start
* if it is not already started; false otherwise.
*
* @return true if the server is definitely known to be started, false otherwise.
*/
public boolean ensureStarted(boolean waitIfNotStarted) {
if (isRunning()) {
return true;
}
if (!canStart()) {
return false;
}
if (waitIfNotStarted) {
return start(START_TIMEOUT);
} else {
start(-1);
return false;
}
}
}