/*****************************************************************
* 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.apache.cayenne.modeler;
import java.awt.Dialog;
import java.awt.Frame;
import java.awt.Window;
import java.io.File;
import java.util.Collection;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JRootPane;
import javax.swing.SwingUtilities;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Transformer;
import org.apache.cayenne.modeler.util.AdapterMapping;
import org.apache.cayenne.modeler.util.CayenneAction;
import org.apache.cayenne.modeler.util.CayenneDialog;
import org.apache.cayenne.pref.Domain;
import org.apache.cayenne.pref.DomainPreference;
import org.apache.cayenne.pref.HSQLEmbeddedPreferenceEditor;
import org.apache.cayenne.pref.HSQLEmbeddedPreferenceService;
import org.apache.cayenne.pref.PreferenceService;
import org.apache.cayenne.project.CayenneUserDir;
import org.apache.cayenne.project.Project;
import org.apache.cayenne.swing.BindingFactory;
import org.scopemvc.controller.basic.ViewContext;
import org.scopemvc.controller.swing.SwingContext;
import org.scopemvc.core.View;
import org.scopemvc.util.UIStrings;
import org.scopemvc.view.swing.SwingView;
/**
* A main modeler application class that provides a number of services to the Modeler
* components. Configuration properties:
* <ul>
* <li>cayenne.modeler.application.name - name of the application, 'CayenneModeler' is
* default. Used to locate prerferences domain among other things.</li>
* <li>cayenne.modeler.pref.version - a version of the preferences DB schema. Default is
* "1.1".</li>
* </ul>
*
* @author Andrus Adamchik
*/
public class Application {
public static final String PREFERENCES_VERSION = "1.2";
public static final String PREFERENCES_DB_SUBDIRECTORY = "prefs";
public static final String PREFERENCES_MAP_PACKAGE = "pref";
public static final String APPLICATION_NAME_PROPERTY = "cayenne.modeler.application.name";
public static final String PREFERENCES_VERSION_PROPERTY = "cayenne.modeler.pref.version";
public static final String DEFAULT_APPLICATION_NAME = "CayenneModeler";
// TODO: implement cleaner IoC approach to avoid using this singleton...
protected static Application instance;
protected FileClassLoadingService modelerClassLoader;
protected HSQLEmbeddedPreferenceService preferenceService;
protected ActionManager actionManager;
protected CayenneModelerController frameController;
protected File initialProject;
protected String name;
protected String preferencesDB;
protected BindingFactory bindingFactory;
protected AdapterMapping adapterMapping;
public static Application getInstance() {
return instance;
}
// static methods that should probabaly go away eventually...
public static CayenneModelerFrame getFrame() {
return (CayenneModelerFrame) getInstance().getFrameController().getView();
}
public static Project getProject() {
return getInstance().getFrameController().getProjectController().getProject();
}
public Application(File initialProject) {
this.initialProject = initialProject;
// configure startup settings
String configuredName = System.getProperty(APPLICATION_NAME_PROPERTY);
this.name = (configuredName != null) ? configuredName : DEFAULT_APPLICATION_NAME;
String subdir = System.getProperty(PREFERENCES_VERSION_PROPERTY);
if (subdir == null) {
subdir = PREFERENCES_VERSION;
}
File dbDir = new File(CayenneUserDir.getInstance().resolveFile(
PREFERENCES_DB_SUBDIRECTORY), subdir);
dbDir.mkdirs();
this.preferencesDB = new File(dbDir, "db").getAbsolutePath();
}
public String getName() {
return name;
}
public ClassLoadingService getClassLoadingService() {
return modelerClassLoader;
}
public AdapterMapping getAdapterMapping() {
return adapterMapping;
}
/**
* Returns an action for key.
*/
public CayenneAction getAction(String key) {
return getActionManager().getAction(key);
}
/**
* Returns action controller.
*/
public ActionManager getActionManager() {
return actionManager;
}
/**
* Returns controller for the main frame.
*/
public CayenneModelerController getFrameController() {
return frameController;
}
/**
* Starts the application.
*/
public void startup() {
// init subsystems
initPreferences();
initClassLoader();
this.bindingFactory = new BindingFactory();
this.adapterMapping = new AdapterMapping();
// ...Scope
// TODO: this will go away if switch away from Scope
// force Scope to use CayenneModeler properties
UIStrings.setPropertiesName(ModelerConstants.DEFAULT_MESSAGE_BUNDLE);
ViewContext.clearThreadContext();
// init actions before the frame, as it will attempt to build menus out of
// actions.
this.actionManager = new ActionManager(this);
// ...create main frame
this.frameController = new CayenneModelerController(this, initialProject);
// update Scope to work nicely with main frame
ViewContext.setGlobalContext(new ModelerContext(frameController.getFrame()));
// open up
frameController.startupAction();
}
public BindingFactory getBindingFactory() {
return bindingFactory;
}
/**
* Returns Application preferences service.
*/
public PreferenceService getPreferenceService() {
return preferenceService;
}
/**
* Returns top preferences Domain for the application.
*/
public Domain getPreferenceDomain() {
return getPreferenceService().getDomain(getName(), true);
}
/**
* Returns a new instance of CodeTemplateManager.
*/
public CodeTemplateManager getCodeTemplateManager() {
return new CodeTemplateManager(this);
}
/**
* Reinitializes ModelerClassLoader from preferences.
*/
public void initClassLoader() {
final FileClassLoadingService classLoader = new FileClassLoadingService();
// init from preferences...
Domain classLoaderDomain = getPreferenceDomain().getSubdomain(
FileClassLoadingService.class);
Collection details = classLoaderDomain.getPreferences();
if (details.size() > 0) {
// transform preference to file...
Transformer transformer = new Transformer() {
public Object transform(Object object) {
DomainPreference pref = (DomainPreference) object;
return new File(pref.getKey());
}
};
classLoader.setPathFiles(CollectionUtils.collect(details, transformer));
}
this.modelerClassLoader = classLoader;
// set as EventDispatch thread default class loader
if (SwingUtilities.isEventDispatchThread()) {
Thread.currentThread().setContextClassLoader(classLoader.getClassLoader());
}
else {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
Thread.currentThread().setContextClassLoader(
classLoader.getClassLoader());
}
});
}
}
protected void initPreferences() {
HSQLEmbeddedPreferenceService service = new HSQLEmbeddedPreferenceService(
preferencesDB,
PREFERENCES_MAP_PACKAGE,
getName());
service.stopOnShutdown();
this.preferenceService = service;
this.preferenceService.startService();
// test service
getPreferenceDomain();
}
static final class PreferencesDelegate implements
HSQLEmbeddedPreferenceEditor.Delegate {
static final String message = "Preferences Database is locked by another application. "
+ "Do you want to remove the lock?";
static final String failureMessage = "Failed to remove database lock. "
+ "Preferences will we saved for this session only.";
static final HSQLEmbeddedPreferenceEditor.Delegate sharedInstance = new PreferencesDelegate();
public boolean deleteMasterLock(File lock) {
int result = JOptionPane.showConfirmDialog(null, message);
if (result == JOptionPane.YES_OPTION || result == JOptionPane.OK_OPTION) {
if (!lock.delete()) {
JOptionPane.showMessageDialog(null, failureMessage);
return false;
}
}
return true;
}
}
final class ModelerContext extends SwingContext {
JFrame frame;
public ModelerContext(JFrame frame) {
this.frame = frame;
}
protected void showViewInPrimaryWindow(SwingView view) {
}
/**
* Creates closeable dialogs.
*/
protected void showViewInDialog(SwingView inView) {
// NOTE:
// copied from superclass, except that JDialog is substituted for
// CayenneDialog
// Keep in mind when upgrading Scope to the newer versions.
// Make a JDialog to contain the view.
Window parentWindow = getDefaultParentWindow();
final CayenneDialog dialog;
if (parentWindow instanceof Dialog) {
dialog = new CayenneDialog((Dialog) parentWindow);
}
else {
dialog = new CayenneDialog((Frame) parentWindow);
}
// Set title, modality, resizability
if (inView.getTitle() != null) {
dialog.setTitle(inView.getTitle());
}
if (inView.getDisplayMode() == SwingView.MODAL_DIALOG) {
dialog.setModal(true);
}
else {
dialog.setModal(false);
}
dialog.setResizable(inView.isResizable());
setupWindow(dialog.getRootPane(), inView, true);
dialog.toFront();
}
/**
* Overrides super implementation to allow using Scope together with normal Swing
* code that CayenneModeler already has.
*/
public JRootPane findRootPaneFor(View view) {
JRootPane pane = super.findRootPaneFor(view);
if (pane != null) {
return pane;
}
if (((SwingView) view).getDisplayMode() != SwingView.PRIMARY_WINDOW) {
return pane;
}
return frame.getRootPane();
}
protected Window getDefaultParentWindow() {
return frame;
}
}
}