Asked in Liferay Forums
I've created a permission helper class for my Question entity following the steps explained here:
package net.carlosduran.nomedes.web.internal.security.permission.resource;
import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.security.permission.PermissionChecker;
import com.liferay.portal.kernel.security.permission.resource.ModelResourcePermission;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import net.carlosduran.nomedes.db.model.Question;
#Component(immediate = true)
public class QuestionPermission {
public static boolean contains(
PermissionChecker permissionChecker, Question question, String actionId) throws PortalException {
return _questionModelResourcePermission.contains(permissionChecker, question, actionId);
}
public static boolean contains(
PermissionChecker permissionChecker, long entryId, String actionId) throws PortalException {
return _questionModelResourcePermission.contains(permissionChecker, entryId, actionId);
}
#Reference(
target = "(model.class.name=net.carlosduran.nomedes.db.model.Question)",
unbind = "-")
protected void setEntryModelPermission(ModelResourcePermission<Question> modelResourcePermission) {
_questionModelResourcePermission = modelResourcePermission;
}
private static ModelResourcePermission<Question> _questionModelResourcePermission;
}
In a MVCRenderCommand class I reference it this way:
#Reference
protected QuestionPermission _questionPermission;
If I include this reference, the render class doesn't work (I've tried it with different render classes).
In the moment I delete it, the render class works without a problem.
The code for the service.xml file is this:
<?xml version="1.0"?>
<!DOCTYPE service-builder PUBLIC "-//Liferay//DTD Service Builder 7.4.0//EN" "http://www.liferay.com/dtd/liferay-service-builder_7_4_0.dtd">
<service-builder dependency-injector="ds" package-path="net.carlosduran.nomedes.db">
<namespace>Nomedes</namespace>
<entity name="Question" local-service="true" uuid="true">
<!-- PK fields -->
<column name="questionId" primary="true" type="long"></column>
<!-- Group instance -->
<column name="groupId" type="long"></column>
<!-- Audit fields -->
<column name="companyId" type="long"></column>
<column name="userId" type="long"></column>
<column name="userName" type="String"></column>
<column name="createDate" type="Date"></column>
<column name="modifiedDate" type="Date"></column>
<column name="title" type="String"></column>
<column name="summary" type="String"></column>
<column name="description" type="String"></column>
<column name="status" type="int" />
<column name="statusByUserId" type="long" />
<column name="statusByUserName" type="String" />
<column name="statusDate" type="Date" />
<finder name="GroupId" return-type="Collection">
<finder-column name="groupId"></finder-column>
</finder>
<reference entity="Group" package-path="com.liferay.portal"></reference>
</entity>
<exceptions>
<exception>QuestionValidation</exception>
</exceptions>
</service-builder>
Can anyone tell me what's wrong? Thanks
The problem was that I miss to add a service attribute to the component annotation in the QuestionPermission class.
service = QuestionPermission.class
After adding it, it works fine. It would look like this:
#Component(
immediate = true,
service = QuestionPermission.class
)
Related
Given the model Activity containing a bag with models of type Report (one to many). I would like to get a list of all activities, containing the number of reports of each activity. This two queries don't lead to any good, the counter is always 1 (which is wrong):
select act, (select count(r) from act.Reports r) from Activity act
Or:
select act, count( elements(act.Reports) ) from Activity act group by act.ActivityId, act.Title
Is it possible to write a proper query in HQL to solve this easy task?
Thx for any tipps!
sl3dg3
Edit:
Following the mappings. Activity:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class
name="Core.Models.Config.Activity, Core"
table="Activity"
>
<!-- primary key -->
<id name="ActivityId" type="Int32" unsaved-value="0" access="property">
<column name="ActivityId" not-null="true"/>
<generator class="identity" />
</id>
<!-- Properties -->
<many-to-one name="Title" fetch="join" cascade="all"/>
<!-- One-To-Many Reports -->
<bag name="Reports" inverse="true" fetch="join">
<key column="ReportId" />
<one-to-many class="Core.Models.Report"/>
</bag>
</class>
</hibernate-mapping>
Mapping Report:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class
name="Core.Models.Report, Core"
table="Report"
>
<!-- primary key -->
<id name="ReportId" type="Int32" unsaved-value="0" access="property">
<column name="ReportId" not-null="true"/>
<generator class="identity" />
</id>
<!-- Properties (shortened - there are more many-to-one and one bag -->
<property name="Created" />
<many-to-one name="Activity" column="ActivityId" />
</class>
Class Activity:
public class Activity
{
public Activity()
{
Title = new Translation();
}
public virtual int ActivityId { get; set; }
public virtual Translation Title { get; set; }
public virtual IList<Report> Reports { get; set; }
}
Class Report:
public class Report
{
/// <summary>
/// HNIBERNATE ONLY
/// </summary>
public Report()
{ }
/// <summary>
/// Init Report
/// </summary>
public Report(User author)
{
// ... Shortened
Activity = new Activity();
}
public virtual Activity Activity { get; set; }
}
What you want to achieve is something like this in SQL:
select
act.*,
(select count(*) from Reports rep where rep.id = act.reportId)
from Activity act
It would be easiest using size(), but unfortunately this is not working:
select act, size(act.Reports)
from Activity act
According to the docs, size is not available in the select clause. Interestingly, it actually works with .size, but not with size(), which should actually be equivalent:
select act, act.Reports.size
from Activity act
It may be worth a feature request to also make the function syntax (size()) working.
The officially working group by syntax is cumbersome, because you need to group by all mapped Activity properties:
select act, count(*)
from Activity act left join act.Reports rep
group by act.id, act.Name, act.Whatever
So I tried finally this variant and it seems to be exactly what you need:
select act, (select count(*) from act.Reports)
from Activity act
Your first HQL query has the wrong syntax. Try this instead:
select act, (select count(*) from act.Reports) from Activity act
Your second HQL query cannot work, because you would need all the columns in the GROUP BY clause. Try this instead:
select act.ActivityId, act.Title, count( elements(act.Reports) )
from Activity act
group by act.ActivityId, act.Title
Edit:
Ah, I think this might be the bug:
<bag name="Reports" inverse="true" fetch="join">
<key column="ActivityId" /> <-- instead of ReportId
<one-to-many class="Core.Models.Report"/>
</bag>
I am trying to create a table-per-hierarchy mapping using NHibernate 2.0.1.
I have a base class with properties that exist for each subclass that other classes inherit from. All of these objects are persisted to one table called Messages that contain all of the possible fields for each class. There is a SourceID which is the discriminator and should indicate which Poco to return for each subclass. Here is my current mapping.
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="NS.Core"
namespace="NS.Core.Model">
<class name="BaseMessage" table="Messages">
<id name="MessageID" type="Int64">
<column name="MessageID" />
<generator class="native" />
</id>
<discriminator column="SourceID" type="Int32"/>
<property name="DateCreated" access="property" column="DateCreated" type="DateTime" not-null="true"/>
<property name="DatePublished" access="property" column="DatePublished" type="DateTime"/>
<property name="SourceID" access="property" column="SourceID" type="Int32"/>
<many-to-one name="User" column="UserID" access="property" cascade="none" lazy="false" fetch="join" outer-join="true" />
<subclass name="NMessage" discriminator-value="0">
<property name="Body" access="property" column="Body" type="String"/>
</subclass>
<subclass name="BMessage" discriminator-value="1">
<property name="Title" access="property" column="Title" type="String"/>
<property name="Body" access="property" column="Body" type="String"/>
</subclass>
<subclass name="CMessage" discriminator-value="2">
<property name="Url" access="property" column="Url" type="String"/>
<property name="Body" access="property" column="Body" type="String"/>
</subclass>
</class>
</hibernate-mapping>
I get an error querying saying Could not format discriminator value to SQL string of entity NS.Core.Model.BaseMessage so I put a discriminator value on this class too althout it should never return the base class. That led me to some antlr errors.
Am I taking the wrong approach to this problem? I would like to query the table and get back a list of different POCOs that all inherit from the base class. It would never return the base class itself.
below is the BaseMessage.cs
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Text;
namespace NS.Core.Model
{
[Serializable]
[DataContract]
[KnownType(typeof(User))]
public class BaseMessage
{
#region Private variables
private long _messageID;
private DateTime? _dateCreated;
private DateTime? _datePublished;
private int _sourceID;
private User _user = new User();
#endregion
#region Properties
[DataMember]
public virtual long MessageID
{
get { return _messageID; }
set { this._messageID = value; }
}
[DataMember]
public virtual DateTime? DateCreated
{
get { return _dateCreated; }
set { this._dateCreated = value; }
}
[DataMember]
public virtual DateTime? DatePublished
{
get { return _datePublished; }
set { this._datePublished = value; }
}
[DataMember]
public virtual int SourceID
{
get { return _sourceID; }
set { this._sourceID = value; }
}
[DataMember]
public virtual User User
{
get
{
if (this._user != null)
{ return this._user; }
else { return new User(); }
}
set { this._user = value; }
}
#endregion
}
}
The approach is sound. I have done exactly this several times.
You could make it explicit in your code my having BaseMessage's default constructor be protected.
You need to declare a discriminator-value on the base class as well.
I favor string values for discriminators, as this clearer when performing SQL queries or reports. Also, since instances of BaseMessage shoudl not exist, in would use null for its discriminator value.
<class name="BaseMessage" table="Messages" discriminator-value="null">
<id />
<discriminator column="SourceID" />
<subclass name="NMessage" discriminator-value="NMessage">
</subclass>
<subclass name="BMessage" discriminator-value="BMessage">
</subclass>
<subclass name="CMessage" discriminator-value="CMessage">
</subclass>
</class>
Also, I see that you have mapped a property to the discriminator column. You should instead have a method that returns something unique to the class - in this case the code.
Note that you cannot change the class of a mapped entity after it has been saved. Not even by changing the discriminator. If you did change it via SQL, your 2nd level cache will still hold a version with the original class.
class BaseMessage
{
public virtual string MessageType { return null; }
}
class NMessage : BaseMessage
{
public override string MessageType { return "NMessage"; }
}
Finally, your mapping file is overly verbose as it includes values which are the default. The following attributes and elements can be removed:
access="property" - this is the default
type="String" - all the types you use can be inferred from your .NET class
column="COL" - default is the same as the name
similarly for the id column element
All of your Message subclasses have property Body, so move it to the base class mapping. If this field can be longer than your database varchar, it should be a text column and have type="StringCLob" which maps to string in .NET
<class name="BaseMessage" table="Messages" discriminator-value="null">
<id name="MessageID">
<generator class="native" />
</id>
<discriminator column="SourceID"/>
<property name="DateCreated" />
<property name="DatePublished" />
<many-to-one name="User" column="UserID" cascade="none" lazy="false" fetch="join" outer-join="true" />
<property name="Body" type="StringCLob" />
<subclass name="NMessage" discriminator-value="NMessage">
</subclass>
<subclass name="BMessage" discriminator-value="BMessage">
<property name="Title" />
</subclass>
<subclass name="CMessage" discriminator-value="CMessage">
<property name="Url" />
</subclass>
</class>
I have a class StoreHours that has a composite key and has been working perfectly. A new demand came up for another type of hours to be returned. I thought "simple, I'll abstract the base class, have two concrete implementations and change my references in the app to one of the new classes". However, upon doing that, my unit tests failed with
X.Test.StoreTest.HoursTest: NHibernate.InstantiationException : Cannot instantiate abstract class or interface: X.Model.StoreHours
My mapping file looks like
<class name="StoreHours" table="StoreHour" abstract="true" discriminator-value="0" >
<composite-id>
<key-many-to-one name="Store"
class="Store"
column="StoreUid"/>
<key-property name="DayOfWeek"
column="DayOfWeekId"
type="System.DayOfWeek" />
</composite-id>
<discriminator column="StoreHourType" type="Byte" />
<property name="OpenMinutes" column="OpenTime" />
<property name="CloseMinutes" column="CloseTime" />
<subclass name="OfficeHours" discriminator-value="1" />
<subclass name="AccessHours" discriminator-value="2" />
</class>
I found someone with similar troubles here and started down their solution path but actually ended up with even more troubles than I started with.
I can persist the records to the database perfectly but onload, NHibernate is trying to instantiate the abstract 'StoreHours' even though I've only got a strongly type set off 'OfficeHours'
This seems like a really trivial requirement so I figure I must be doing something simple wrong. All hints appreciated.
The problem is in the way you are using the composite-id
Table-per-class works with Composite-id, but only if the composite is
implemented as a class
so you need to create a class like
public class StoreHoursCompositeId
{
public virtual Store Store { get; set; }
public virtual DayOfWeek DayOfWeek { get; set; }
// Implement GetHashCode(), is NH-mandatory
// Implement Equals(object obj), is NH-mandatory
}
In your StoreHours object create a property which use the above class (in my example I called it "StoreHoursCompositeId")
Your mapping become:
<class name="StoreHours" table="StoreHour" abstract="true" discriminator-value="0" >
<composite-id name="StoreHoursCompositeId" class="StoreHoursCompositeId">
<key-many-to-one name="Store" class="Store"
column="StoreUid"/>
<key-property name="DayOfWeek"
column="DayOfWeekId"
type="System.DayOfWeek" />
</composite-id>
<discriminator column="StoreHourType" type="Byte" />
<property name="OpenMinutes" column="OpenTime" />
<property name="CloseMinutes" column="CloseTime" />
<subclass name="OfficeHours" discriminator-value="1" />
<subclass name="AccessHours" discriminator-value="2" />
</class>
I had the very same problem and this fixed it for me.
Here is a simplified version of my database model. I have two tables: "Image", and "HostingProvider" which look like this:
[Image]
id
filename
hostingprovider_id
[HostingProvider]
id
base_url
Image HostingproviderId is a many-to-one foreign key relationship to the HostingProvider table. (Each image has one hosting provider).
Essentially I want to be able to have my Image class look like this:
[Image]
Id
base_url
filename
In NHibernate, how can I create a mapping file that will combine the base_url from the HostingProvider table, into the Image class?
What you're looking for is this:
http://ayende.com/Blog/archive/2007/04/24/Multi-Table-Entities-in-NHibernate.aspx
Here's a peek of what it looks like:
<class name="Person">
<id name="Id" column="person_id" unsaved-value="0">
<generator class="native"/>
</id>
<property name="Name"/>
<property name="Sex"/>
<join table="address">
<key column="address_id"/>
<property name="Address"/>
<property name="Zip"/>
<property name="Country"/>
<property name="HomePhone"/>
<property name="BusinessPhone"/>
</join>
</class>
public class Image {
public virtual HostingProvider HostingProvider { get; set; } // NHibernate takes care of this
public virtual string BaseUrl { get { return HostingProvider.BaseUrl; } }
}
I'm using nhibernate to store some user settings for an app in a SQL Server Compact Edition table.
This is an excerpt the mapping file:
<property name="Name" type="string" />
<property name="Value" type="string" />
Name is a regular string/nvarchar(50), and Value is set as ntext in the DB
I'm trying to write a large amount of xml to the "Value" property. I get an exception every time:
#p1 : String truncation: max=4000, len=35287, value='<lots of xml..../>'
I've googled it quite a bit, and tried a number of different mapping configurations:
<property name="Name" type="string" />
<property name="Value" type="string" >
<column name="Value" sql-type="StringClob" />
</property>
That's one example. Other configurations include "ntext" instead of "StringClob". Those configurations that don't throw mapping exceptions still throw the string truncation exception.
Is this a problem ("feature") with SQL CE? Is it possible to put more than 4000 characters into a SQL CE database with nhibernate? If so, can anyone tell me how?
Many thanks!
Okay, with many thanks to Artur in this thread, here's the solution:
Inherit from the SqlServerCeDriver with a new one, and override the InitializeParamter method:
using System.Data;
using System.Data.SqlServerCe;
using NHibernate.Driver;
using NHibernate.SqlTypes;
namespace MySqlServerCeDriverNamespace
{
/// <summary>
/// Overridden Nhibernate SQL CE Driver,
/// so that ntext fields are not truncated at 4000 characters
/// </summary>
public class MySqlServerCeDriver : SqlServerCeDriver
{
protected override void InitializeParameter(
IDbDataParameter dbParam,
string name,
SqlType sqlType)
{
base.InitializeParameter(dbParam, name, sqlType);
if (sqlType is StringClobSqlType)
{
var parameter = (SqlCeParameter)dbParam;
parameter.SqlDbType = SqlDbType.NText;
}
}
}
}
Then, use this driver instead of NHibernate's in your app.config
<nhibernateDriver>MySqlServerCeDriverNamespace.MySqlServerCeDriver , MySqlServerCeDriverNamespace</nhibernateDriver>
I saw a lot of other posts where people had this problem, and solved it by just changing the sql-type attribute to "StringClob" - as attempted in this thread.
I'm not sure why it wouldn't work for me, but I suspect it is the fact that I'm using SQL CE and not some other DB. But, there you have it!
<property name="Value" type="string" />
<column name="Value" sql-type="StringClob" />
</property>
I'm assuming this is a small typo, since you've closed the property tag twice. Just pointing this out, in case it wasn't a typo.
Try <property name="Value" type="string" length="4001" />
Tried:
<property name="Value" type="string" length="4001" />
and
<property name="Value" type="string" >
<column name="Value" sql-type="StringClob" length="5000"/>
</property>
Neither worked, I'm afraid... Same exception - it still says that the max value is 4000.
Why are you using the sub-element syntax?
try:
<property name='Value' type='StringClob' />
On my current deplyoment of SQL CE and NHibernate I use a length of 4001. Then NHibernate generates the stuff as NTEXT instead of NVARCHAR.
Try that.
Another thing to use with NHibernate and SQL CE is:
<session-factory>
...
<property name="connection.release_mode">on_close</property>
</session-factory>
That solves some other problems for me atleast.
After reading your post this modification got it working in my code
protected override void InitializeParameter(IDbDataParameter dbParam,string name,SqlType sqlType)
{
base.InitializeParameter(dbParam, name, sqlType);
var stringType = sqlType as StringSqlType;
if (stringType != null && stringType.LengthDefined && stringType.Length > 4000)
{
var parameter = (SqlCeParameter)dbParam;
parameter.SqlDbType = SqlDbType.NText;
}
}