Renaming composite foreign keys in GORM - grails

I have the following classes:
class Catalog {
static mapping = {
id composite:['name', 'manufacturer']
columns {
name column:'cat_name'
manufacturer column:'manuf_id'
}
}
String name
Manufacturer manufacturer
}
class Order {
static mapping = {
columns {
// How to rename foreign keys as cat_name, manuf_id?
}
}
Catalog catalog // creates catalog_name, catalog_manufacturer_name
}
Presently, an Order table is generated with the attributes catalog_name and catalog_manufacturer_name (which reference the composite primary keys of the Catalog table).
I need to rename these generated columns to cat_name and manuf_id in the Order table to work with an existing database. Is this possible, and if so, how?

This solved my problem (grails 2.0.4):
http://jira.grails.org/browse/GRAILS-4504?focusedCommentId=64996&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-64996
My case:
class GroupMessage implements Serializable {
Group group
Integer messageId
static mapping = {
datasources(['ds1'])
table 't_group_msg'
version false
id composite: ['group', 'messageId'], generator: 'assigned'
group column:'grpid'
messageId column:'msgid', type:int
}
}
class GroupMessageDetail implements Serializable {
GroupMessage groupMessage
Integer detailId
String message
String url
static mapping = {
datasources(['ds1'])
table 't_group_msg_det'
version false
id composite: ['groupMessage', 'detailId'], generator: 'assigned'
columns {
groupMessage {
column name: 'grpid'
column name: 'msgid'
}
detailId column:'id', type:int
message column:'sms'
url column:'url'
}
}

It's not possible using GORM configuration, but you can do it with a custom Configuration class:
package com.foo.bar;
import java.util.Collection;
import java.util.Iterator;
import org.codehaus.groovy.grails.orm.hibernate.cfg.DefaultGrailsDomainConfiguration;
import org.hibernate.MappingException;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.RootClass;
public class CompositeAwareHibernateConfiguration extends DefaultGrailsDomainConfiguration {
private static final long serialVersionUID = 1;
private boolean _alreadyProcessed;
#SuppressWarnings("unchecked")
#Override
protected void secondPassCompile() throws MappingException {
super.secondPassCompile();
if (_alreadyProcessed) {
return;
}
for (PersistentClass pc : (Collection<PersistentClass>)classes.values()) {
if (pc instanceof RootClass) {
RootClass root = (RootClass)pc;
if ("com.foo.bar.Order".equals(root.getClassName())) {
for (Iterator iter = root.getTable().getColumnIterator(); iter.hasNext(); ) {
Column column = (Column)iter.next();
if ("catalog_name".equals(column.getName())) {
column.setName("cat_name");
}
else if ("catalog_manufacturer_id".equals(column.getName())) {
column.setName("manuf_id");
}
}
}
}
}
_alreadyProcessed = true;
}
}
Put the class in src/java and register it in DataSource.groovy:
dataSource {
pooled = true
driverClassName = ...
username = ...
password = ...
configClass = com.foo.bar.CompositeAwareHibernateConfiguration
}

I have write a solution that is for any domain-class that need it and you don't need readapt every time.
class Catalog {
static mapping = {
id composite:['name', 'manufacturer']
columns {
name column:'cat_name'
manufacturer column:'manuf_id'
}
}
String name
Manufacturer manufacturer
}
class Order {
Catalog catalog
static mapping = {
}
static foreigners = [
catalog : [name : "catalog_name",
manufacturer: "catalog_manufacturer_name"]
]
}
This is the GORM Configuration class that i write to consume the foreigners in the domain class.
package my.app.package
import java.util.Collection;
import org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsAnnotationConfiguration;
import org.hibernate.MappingException;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.ForeignKey;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.RootClass;
class MyCustomGrailsAnotationConfiguration extends GrailsAnnotationConfiguration{
private static final long serialVersionUID = 1;
private boolean _alreadyProcessed=false;
#SuppressWarnings("unchecked")
#Override
protected void secondPassCompile() throws MappingException {
super.secondPassCompile();
if(_alreadyProcessed){
return;
}
classes.values().each{rootClass ->
if(rootClass instanceof RootClass){
def domainClass= null
Boolean hasForeigners=false
try{
domainClass=Class.forName(rootClass.entityName,false,Thread.currentThread().getContextClassLoader())
hasForeigners = domainClass.metaClass.hasProperty(domainClass, 'foreigners')
}catch(Exception e){}
if(domainClass && hasForeigners){
rootClass?.table?.foreignKeyIterator?.each{fKey->
fKey?.columnIterator?.each{column->
domainClass.foreigners?.each{attrName,columns ->
columns.each{columnItmName,columnItmValue->
def exp=attrName+"_"
columnItmName.split("").each{exp+=(it==~/[A-Z]/) ? "_"+it:it}
exp=exp.toLowerCase()+".(id)\$"
//println "Expression:"+exp
if(column.name.matches(exp)){
//println "Match:"+column.name+" changing to "+columnItmValue
column.name=columnItmValue
}
}
}
}
}
}
}
}
_alreadyProcessed = true;
}
}
Put the my.app.package.MyCustomGrailsAnotationConfiguration.groovy class in src/groovy/my/app/package/MyCustomGrailsAnotationConfiguration.groovy and register it in DataSource.groovy:
dataSource {
pooled = true
driverClassName = ...
username = ...
password = ...
configClass = my.app.package.MyCustomGrailsAnotationConfiguration
}
I hope that will useful for you.
Thanks #carlosmain for your help

Related

Grails 2.5.4: Get all logged in users with HttpSessionListener

I'm trying to get all the current user sessions using the app-info plugin but it doesn't compile in Grails 2.5.4. Is there another way to get this information? I'm new to Grails.
This link has some information including a reference to the app-info plugin but it's old and I wasn't able to get anything to compile.
UPDATE
I've added this custom class in src\groovy\UserSessions:
package usersessions
import javax.servlet.http.HttpSessionEvent
import javax.servlet.http.HttpSessionListener
//import org.apache.shiro.subject.PrincipalCollection
//import org.apache.shiro.subject.SimplePrincipalCollection;
//import org.apache.shiro.subject.support.DefaultSubjectContext
class MyHttpSessionListener implements HttpSessionListener {
private static List activeUsers = Collections.synchronizedList(new ArrayList());
#Override
public void sessionCreated(HttpSessionEvent event) { }
#Override
public void sessionDestroyed(HttpSessionEvent event) {
String userName = getCurrentUserName(event)
if (userName == null) {
return
}
MyHttpSessionListener.userLoggedOut(userName)
}
public static void userLoggedIn(String userName) {
if (!activeUsers.contains(userName)) {
activeUsers.add(userName)
}
}
public static void userLoggedOut(String userName) {
activeUsers.remove(userName)
}
public static List getCurrentUserNames() {
return Collections.unmodifiableList(activeUsers);
}
/*
private String getCurrentUserName(HttpSessionEvent event) {
PrincipalCollection currentUser = (PrincipalCollection) event.getSession().getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
if (currentUser == null) {
return null
}
return (String) currentUser.getPrimaryPrincipal();
}
*/
}
Added this to /scripts/_Events.groovy
import groovy.xml.StreamingMarkupBuilder
eventWebXmlEnd = {String tmpfile ->
def root = new XmlSlurper().parse(webXmlFile)
root.appendNode {
'listener' {
'listener-class' (
'usersessions.MyHttpSessionListener'
)
}
}
webXmlFile.text = new StreamingMarkupBuilder().bind {
mkp.declareNamespace(
"": "http://java.sun.com/xml/ns/javaee")
mkp.yield(root)
}
}
Added this to resources.groovy
myHttpSessionListener(usersessions.MyHttpSessionListener)
/* shiroAuthenticator(ModularRealmAuthenticator) {
authenticationStrategy = ref("shiroAuthenticationStrategy")
authenticationListeners = [ ref("authListener") ]
}
*/
Added this to my controller:
package usersessions
class NewController extends MyHttpSessionListener {
def index() {
myHttpSessionListener.userLoggedIn("user1")
render "Users "+ getCurrentUserNames()
}
}
It compiles and the app runs but I don't see how to get sessions in the listener? The controller gives an error when I load it:
No such property: myHttpSessionListener for class: usersessions.NewController
UPDATE 2
I changed the controller to use this code to test creating a sessions and it works:
def index() {
String randomString = org.apache.commons.lang.RandomStringUtils.random(3, true, true)
userLoggedIn(randomString)
render "Users "+ getCurrentUserNames()
}

Where can I find the source code for ActivityTestRule?

Where can I find the source code for ActivityTestRule?
You can find it at
https://android.googlesource.com/platform/frameworks/testing/+/android-support-test/rules/src/main/java/android/support/test/rule/ActivityTestRule.java
You have to use branch android-support-test instead of master on platform/frameworks/testing project in AOSP.
You can add ActivityTestRule in code, import library and CTRL+Left Click to decompile.
If you can't import this class, you need to add it to dependencies in your build.gradle
androidTestCompile 'com.android.support.test:rules:0.3'
EDIT:
My result of decompiling:
package android.support.test.rule;
import android.app.Activity;
import android.app.Instrumentation;
import android.content.Intent;
import android.support.annotation.Nullable;
import android.support.test.InstrumentationRegistry;
import android.support.test.annotation.Beta;
import android.support.test.internal.util.Checks;
import android.support.test.rule.UiThreadTestRule;
import android.util.Log;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
#Beta
public class ActivityTestRule<T extends Activity> extends UiThreadTestRule {
private static final String TAG = "ActivityInstrumentationRule";
private final Class<T> mActivityClass;
private Instrumentation mInstrumentation;
private boolean mInitialTouchMode;
private boolean mLaunchActivity;
private T mActivity;
public ActivityTestRule(Class<T> activityClass) {
this(activityClass, false);
}
public ActivityTestRule(Class<T> activityClass, boolean initialTouchMode) {
this(activityClass, initialTouchMode, true);
}
public ActivityTestRule(Class<T> activityClass, boolean initialTouchMode, boolean launchActivity) {
this.mInitialTouchMode = false;
this.mLaunchActivity = false;
this.mActivityClass = activityClass;
this.mInitialTouchMode = initialTouchMode;
this.mLaunchActivity = launchActivity;
this.mInstrumentation = InstrumentationRegistry.getInstrumentation();
}
protected Intent getActivityIntent() {
return new Intent("android.intent.action.MAIN");
}
protected void beforeActivityLaunched() {
}
protected void afterActivityLaunched() {
}
protected void afterActivityFinished() {
}
public T getActivity() {
if(this.mActivity == null) {
Log.w("ActivityInstrumentationRule", "Activity wasn\'t created yet");
}
return this.mActivity;
}
public Statement apply(Statement base, Description description) {
return new ActivityTestRule.ActivityStatement(super.apply(base, description));
}
public T launchActivity(#Nullable Intent startIntent) {
this.mInstrumentation.setInTouchMode(this.mInitialTouchMode);
String targetPackage = this.mInstrumentation.getTargetContext().getPackageName();
if(null == startIntent) {
startIntent = this.getActivityIntent();
if(null == startIntent) {
Log.w("ActivityInstrumentationRule", "getActivityIntent() returned null using default: Intent(Intent.ACTION_MAIN)");
startIntent = new Intent("android.intent.action.MAIN");
}
}
startIntent.setClassName(targetPackage, this.mActivityClass.getName());
startIntent.addFlags(268435456);
Log.d("ActivityInstrumentationRule", String.format("Launching activity %s", new Object[]{this.mActivityClass.getName()}));
this.beforeActivityLaunched();
this.mActivity = (Activity)this.mActivityClass.cast(this.mInstrumentation.startActivitySync(startIntent));
this.mInstrumentation.waitForIdleSync();
this.afterActivityLaunched();
return this.mActivity;
}
void setInstrumentation(Instrumentation instrumentation) {
this.mInstrumentation = (Instrumentation)Checks.checkNotNull(instrumentation, "instrumentation cannot be null!");
}
void finishActivity() {
if(this.mActivity != null) {
this.mActivity.finish();
this.mActivity = null;
}
}
private class ActivityStatement extends Statement {
private final Statement mBase;
public ActivityStatement(Statement base) {
this.mBase = base;
}
public void evaluate() throws Throwable {
try {
if(ActivityTestRule.this.mLaunchActivity) {
ActivityTestRule.this.mActivity = ActivityTestRule.this.launchActivity(ActivityTestRule.this.getActivityIntent());
}
this.mBase.evaluate();
} finally {
ActivityTestRule.this.finishActivity();
ActivityTestRule.this.afterActivityFinished();
}
}
}
}

Grails domain class transient collection attribute setter issue

I am trying to set (or bind) value to a transient List attribute. But I failed with collections.
On the other hand transient String attribute working well on setter.
Grails version 2.4.3
Any advice?
#Resource(uri = "/api/samples", formats = ["json"])
class Sample {
static transients = ["fields","sample"]
String regions
String name
String sample
List<String> fields
List<String> getFields() {
this.regions != null ? Arrays.asList(regions.split("\\s*,\\s*")) : new ArrayList<String>();
}
void setFields(List<String> fields) {
if (fields != null && !fields.isEmpty()) {
this.regions = fields.join(",")
}
}
void setSample(String sample){
this.name = sample
}
static mapping = {
}
}
Untyped fields are transient by default, so this alternative approach should work (and is a lot more concise):
#Resource(uri = "/api/samples", formats = ["json"])
class Sample {
static transients = ["sample"]
String regions
String name
String sample
def getFields() {
this.regions != null ? Arrays.asList(regions.split("\\s*,\\s*")) : []
}
void setFields(def fieldList) {
if (fieldList) {
this.regions = fieldList.join(",")
}
}
void setSample(String sample){
this.name = sample
}
static mapping = {
}
}

Grails search two child objects

I have three domain objects
class OrgProfile {
String name
static mapping = {
discriminator column:'ORG_TYPE'
}
}
class Org extends OrgProfile {
static mapping = {
discriminator 'ORG'
}
}
class Jurisdiction extends OrgProfile {
String email
static mapping{
discriminator 'JURISDICTION'
}
}
I need to search by name and email to get all list of Org and Jurisdiction
so something like
def criteria = OrgProfile.createCriteria()
criteria.list{
or {
ilike("name", "%${token}%")
ilike("email", "%${token}%")
}
}
where token is a string. How can this be achieved?
Tried the code:
def criteria = OrgProfile.createCriteria()
def results = criteria.list{
or {
ilike("name", "%${token}%")
ilike("email", "%${token}%")
}
}
Results as expected.

db4o Transparent Persistence Not Working

Using db4o client/server, updates are not working for collection properties of an object. I'm using transparent persistence, but that's not helping. Then, I changed my Collection property to ActivatableCollection, but no luck.
This is the server setup:
private void StartDatabase()
{
IServerConfiguration serverConfiguration = Db4oClientServer.NewServerConfiguration();
serverConfiguration.Networking.MessageRecipient = this;
serverConfiguration.Common.Add(new TransparentActivationSupport());
serverConfiguration.Common.Add(new TransparentPersistenceSupport());
string db4oDatabasePath = AppDomain.CurrentDomain.BaseDirectory;
string db4oDatabaseFileName = ConfigurationManager.AppSettings["db4oDatabaseFileName"];
int databaseServerPort = Convert.ToInt32(ConfigurationManager.AppSettings["databaseServerPort"], CultureInfo.InvariantCulture);
_db4oServer = Db4oClientServer.OpenServer(serverConfiguration, db4oDatabasePath + db4oDatabaseFileName, databaseServerPort);
string databaseUser = ConfigurationManager.AppSettings["databaseUser"];
string databasePassword = ConfigurationManager.AppSettings["databasePassword"];
_db4oServer.GrantAccess(databaseUser, databasePassword);
}
This is the entity that I want to save:
public class Application : ActivatableEntity
And this is the property within the Application entity:
public ActivatableCollection<TaskBase> Tasks { get; private set; }
This is the client code to update each object within the collection:
Application application = (from Application app in db
where app.Name == "Foo"
select app).FirstOrDefault();
foreach (TaskBase task in application.Tasks)
{
task.Description += ".";
}
db.Store(application);
Curiously, db.Commit() didn't work either.
There are two work-arounds, but I'd rather do this the "right" way.
Work-around 1: Call db.Store(task) on each task as the change is made.
Work-around 2: Before calling db.Store(), do this:
db.Ext().Configure().UpdateDepth(5);
Can anyone tell me why the list isn't updating?
If it helps, here is the ActivatableCollection class:
public class ActivatableCollection<T> : Collection<T>, IActivatable
{
[Transient]
private IActivator _activator;
/// <summary>
/// Activates the specified purpose.
/// </summary>
/// <param name="purpose">The purpose.</param>
public void Activate(ActivationPurpose purpose)
{
if (this._activator != null)
{
this._activator.Activate(purpose);
}
}
/// <summary>
/// Binds the specified activator.
/// </summary>
/// <param name="activator">The activator.</param>
public void Bind(IActivator activator)
{
if (_activator == activator) { return; }
if (activator != null && null != _activator) { throw new System.InvalidOperationException(); }
_activator = activator;
}
}
Indeed, transparent persistence needs a call to it's activator before every field access. However the intentions is that you do this with the enhancer-tool instead of implementing manually.
Another note: When you're using CascadeOnUpdate(true) everywhere db4o will end up storing every reachable activated object. If the object-graph is huge, this can be a major performance bottle-neck.
I was able to get transparent activation and persistence to work. I decided not to go with the approach for the reasons mentioned in my comment above. I think the easiest way to handle cascading updates is to simply use a client config like this:
IClientConfiguration clientConfig = Db4oClientServer.NewClientConfiguration();
And then either a bunch of these (this isn't so bad because we can add an attribute to every domain entity, then reflectively do this on each one):
clientConfig.Common.ObjectClass(typeof(Application)).CascadeOnUpdate(true);
Or this:
clientConfig.Common.UpdateDepth = 10;
return Db4oClientServer.OpenClient(clientConfig, databaseServerName, databaseServerPort, databaseUser, databasePassword);
Now, here is the server config that allowed me to get transparent persistence working.
private void StartDatabase()
{
IServerConfiguration serverConfiguration = Db4oClientServer.NewServerConfiguration();
serverConfiguration.Networking.MessageRecipient = this;
serverConfiguration.Common.Add(new TransparentActivationSupport());
serverConfiguration.Common.Add(new TransparentPersistenceSupport());
string db4oDatabasePath = AppDomain.CurrentDomain.BaseDirectory;
string db4oDatabaseFileName = ConfigurationManager.AppSettings["db4oDatabaseFileName"];
int databaseServerPort = Convert.ToInt32(ConfigurationManager.AppSettings["databaseServerPort"], CultureInfo.InvariantCulture);
_db4oServer = Db4oClientServer.OpenServer(serverConfiguration, db4oDatabasePath + db4oDatabaseFileName, databaseServerPort);
string databaseUser = ConfigurationManager.AppSettings["databaseUser"];
string databasePassword = ConfigurationManager.AppSettings["databasePassword"];
_db4oServer.GrantAccess(databaseUser, databasePassword);
}
Hope this helps someone.
I had the same problem with Transparent Activation and Persistence in java. I managed to get it to work cleaning the database and starting from scratch. However, no works by calling commit() after changing the object graph. You must call store() on the root object.
This is a simple example:
/*************** Item.java ******************************************/
import com.db4o.activation.ActivationPurpose;
import com.db4o.activation.Activator;
import com.db4o.collections.ActivatableSupport;
import com.db4o.ta.Activatable;
public class Item implements Activatable {
private String name;
private transient Activator activator;
public Item(String name) {
this.name = name;
}
public String getName() {
activate(ActivationPurpose.READ);
return name;
}
public void setName(String name) {
activate(ActivationPurpose.WRITE);
this.name = name;
}
#Override
public String toString() {
activate(ActivationPurpose.READ);
return "Item [name=" + name + "]";
}
public void activate(ActivationPurpose purpose) {
ActivatableSupport.activate(this.activator, purpose);
}
public void bind(Activator activator) {
this.activator = ActivatableSupport.validateForBind(this.activator, activator);
}
}
/******************* Container.java *********************************/
import java.util.Set;
import com.db4o.activation.ActivationPurpose;
import com.db4o.activation.Activator;
import com.db4o.collections.ActivatableHashSet;
import com.db4o.collections.ActivatableSupport;
import com.db4o.ta.Activatable;
public class Container implements Activatable {
private String name;
private Set<Item> items;
private transient Activator activator;
public Container() {
items = new ActivatableHashSet<Item>();
}
public String getName() {
activate(ActivationPurpose.READ);
return name;
}
public void setName(String name) {
activate(ActivationPurpose.WRITE);
this.name = name;
}
public void addItem(Item item) {
activate(ActivationPurpose.WRITE);
items.add(item);
}
public Set<Item> getItems() {
activate(ActivationPurpose.READ);
return items;
}
#Override
public String toString() {
activate(ActivationPurpose.READ);
return "Container [items=" + items + "]";
}
public void activate(ActivationPurpose purpose) {
ActivatableSupport.activate(this.activator, purpose);
}
public void bind(Activator activator) {
this.activator = ctivatableSupport.validateForBind(this.activator, activator);
}
}
/************* Main.java ********************************************/
import com.db4o.Db4oEmbedded;
import com.db4o.ObjectContainer;
import com.db4o.ObjectSet;
import com.db4o.config.EmbeddedConfiguration;
import com.db4o.ta.TransparentActivationSupport;
import com.db4o.ta.TransparentPersistenceSupport;
public class Main {
public static void main() {
EmbeddedConfiguration config = Db4oEmbedded.newConfiguration();
config.common().add(new TransparentActivationSupport());
config.common().add(new TransparentPersistenceSupport());
ObjectContainer db = Db4oEmbedded.openFile(config, System.getProperty("user.home") + "/testTP.db4o");
Container c = new Container();
c.setName("Container0");
ObjectSet<Container> result = db.queryByExample(c);
if(result.hasNext()) {
c = result.next();
System.out.println(c);
}
c.addItem(new Item("Item" + c.getItems().size()));
db.store(c);
System.out.println(c);
db.close();
}
}

Resources