1 /*
2  * Entity - Entity is an object-relational mapping tool for the D programming language. Referring to the design idea of JPA.
3  *
4  * Copyright (C) 2015-2018  Shanghai Putao Technology Co., Ltd
5  *
6  * Developer: HuntLabs.cn
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11  
12 module hunt.entity.Entity;
13 
14 import hunt.entity;
15 import std.string;
16 import std.traits;
17 
18 import hunt.logging;
19 
20 // TODO: Tasks pending completion -@zhangxueping at 2021-02-19T15:26:35+08:00
21 // 
22 __gshared EntityMetaInfo[string] avaliableEntities;
23 
24 mixin template MakeModel()
25 {
26     import hunt.serialization.Common;
27     import hunt.validation;
28     import hunt.logging;
29     import std.format;
30     import hunt.entity.EntityMetaInfo;
31 
32     void onInitialized() {
33     }
34 
35     // pragma(msg, makeGetFunction!(typeof(this)));
36 
37     // FIXME: Needing refactor or cleanup -@zhangxueping at 2020-08-28T16:59:53+08:00
38     // 
39     // enum EntityMetaInfo metaInfo = extractEntityInfo!(typeof(this));
40 
41     static EntityMetaInfo metaInfo() {
42         enum m = extractEntityInfo!(typeof(this));
43         if(!_isRegistered) {
44             _isRegistered = true;
45             synchronized {
46                 avaliableEntities[m.fullName] = m;
47             }
48         }
49         return m;
50     }
51 
52     private static bool _isRegistered = false;
53 
54     mixin MakeLazyData;
55 
56     mixin MakeLazyLoadList!(typeof(this));
57     mixin MakeLazyLoadSingle!(typeof(this));
58     mixin(makeGetFunction!(typeof(this)));
59     mixin MakeValid;
60     /*  
61      *   NOTE: annotation following code forbid auto create tables function when the EntityFactory construct for the reason when 
62      *   the Entity model import other Entity model cause the dlang bug like "object.Error@src/rt/minfo.d(371): Cyclic dependency between 
63      *   module SqlStruct.User and SqlStruct.Blog".
64      *   if you want use the auto create tables function, you can open the following code and put all Entity model into one d file or 
65      *   use EntityManagerFactory.createTables!(User,Blog) after EntityManagerFactory construct.
66      */
67     // shared static this() {
68     //     addCreateTableHandle(getEntityTableName!(typeof(this)), &onCreateTableHandler!(typeof(this)));
69     // }
70 }
71 
72 mixin template MakeLazyData() {
73     import hunt.logging;
74 
75     @Ignore
76     private LazyData[string] _lazyDatas;
77 
78     @Ignore
79     private EntityManager _manager;
80 
81     void setManager(EntityManager manager) {_manager = manager;}
82     EntityManager getManager() {return _manager;}
83 
84     void addLazyData(string key, LazyData data) {
85         if (data is null) {
86             version(HUNT_ENTITY_DEBUG) warningf("No data for %s", key);
87         } else {
88             _lazyDatas[key] = data;
89         }
90     }
91 
92     LazyData[string] getAllLazyData() {
93         return _lazyDatas;
94     }
95 
96     bool hasLazyData(string key) {
97         auto itemPtr = key in _lazyDatas;
98         return itemPtr !is null;
99     }
100 
101     LazyData getLazyData(string key ) {
102         version(HUNT_ENTITY_DEBUG) {
103             tracef("key: %s, lazyDatas size: %d", key, _lazyDatas.length);
104         }
105 
106         auto itemPtr = key in _lazyDatas;
107         
108         if(itemPtr is null) {
109             warningf("No data found for [%s]", key);
110             return null;
111         } else {
112             return *itemPtr;
113         }
114     }    
115 }
116 
117 mixin template MakeLazyLoadList(T) {
118 
119     private R[] lazyLoadList(R)(LazyData data , bool manyToMany = false , string mapped = "") {
120         import hunt.logging;
121         // logDebug("lazyLoadList ETMANAGER : ",_manager);
122         // assert(data !is null, "The LazyData can't be null");
123         if(data is null) {
124             warningf("The LazyData for %s[] is null", R.stringof);
125             return null;
126         }
127 
128         auto builder = _manager.getCriteriaBuilder();
129         auto criteriaQuery = builder.createQuery!(R, T);
130        
131         // logDebug("****LoadList( %s , %s , %s )".format(R.stringof,data.key,data.value));
132 
133         if(manyToMany)
134         {
135             // logDebug("lazyLoadList for :",mapped);
136             auto r = criteriaQuery.manyToManyFrom(null, this,mapped);
137 
138             auto p = builder.lazyManyToManyEqual(r.get(data.key), data.value, false);
139         
140             auto query = _manager.createQuery(criteriaQuery.select(r).where(p));
141             auto ret = query.getResultList();
142             foreach(v;ret) {
143                 v.setManager(_manager);
144             }
145             return ret;
146         }
147         else
148         {
149             auto r = criteriaQuery.from(null, this);
150             auto p = builder.lazyEqual(r.get(data.key), data.value, false);
151         
152             auto query = _manager.createQuery(criteriaQuery.select(r).where(p));
153             auto ret = query.getResultList();
154             foreach(v;ret) {
155                 v.setManager(_manager);
156             }
157             return ret;
158         }
159         
160     }    
161 }
162 
163 
164 mixin template MakeLazyLoadSingle(T) {
165 
166     import hunt.logging;
167 
168     private R lazyLoadSingle(R)(LazyData data) {
169         if(data is null) {
170             warning("The LazyData for %s is null", R.stringof);
171             return R.init;
172         }
173         auto builder = _manager.getCriteriaBuilder();
174         auto criteriaQuery = builder.createQuery!(R, T);
175         auto r = criteriaQuery.from(null, this);
176         auto p = builder.lazyEqual(r.get(data.key), data.value, false);
177         TypedQuery!(R, T) query = _manager.createQuery(criteriaQuery.select(r).where(p));
178         Object singleResult = query.getSingleResult();
179 
180         if(singleResult is null) {
181             warningf("The result (%s) is null", R.stringof);
182             return R.init;
183         }
184 
185         R ret = cast(R)(singleResult);
186         if(ret is null) {
187             warningf("Type missmatched, expect: %s, actural: %s", typeid(R), typeid(singleResult));
188         }
189         ret.setManager(_manager);
190         return ret;
191     }    
192 }
193 
194 string makeGetFunction(T)() {
195     string str;
196     string allGetMethods;
197 
198     static foreach (string memberName; FieldNameTuple!T) {{
199         alias currentMemeber = __traits(getMember, T, memberName);
200         alias memType = typeof(currentMemeber);
201 
202         static if (__traits(getProtection, currentMemeber) == "public") {
203 
204             static if (hasUDA!(currentMemeber, OneToOne)) {
205                 enum bool lazyLoading = true;
206                 enum FetchType fetchType = getUDAs!(currentMemeber, OneToOne)[0].fetch;
207             } else static if (hasUDA!(currentMemeber, OneToMany)) {
208                 enum bool lazyLoading = true;
209                 enum FetchType fetchType = getUDAs!(currentMemeber, OneToMany)[0].fetch;
210             } else static if (hasUDA!(currentMemeber, ManyToOne)) {
211                 enum bool lazyLoading = true;
212                 enum FetchType fetchType = getUDAs!(currentMemeber, ManyToOne)[0].fetch;
213             } else static if (hasUDA!(currentMemeber, ManyToMany)) {
214                 enum bool lazyLoading = true;
215                 enum FetchType fetchType = getUDAs!(currentMemeber, ManyToMany)[0].fetch;
216             } else {
217                 enum bool lazyLoading = false;
218             }
219 
220             static if (lazyLoading) {
221                 static if(fetchType == FetchType.EAGER && !hasUDA!(currentMemeber, JoinColumn)) {
222                     allGetMethods ~= `
223                         if(` ~ memberName ~ ` is null) {
224                             info("loading data for [` ~ memberName ~ `] in [` ~ T.stringof ~ `]");
225                             get` ~ capitalize(memberName) ~ `();
226                         }
227                     `;
228                 }
229 
230                 str ~= `
231                 public `~memType.stringof ~ " get" ~ capitalize(memberName) ~ `() {`;
232                 
233                 static if (isArray!memType) {
234                     string mappedBy;
235                     static if(hasUDA!(currentMemeber, ManyToMany)) {
236                         str ~= "\n bool manyToMany = true ;";
237                         
238                         mappedBy = "\"" ~ getUDAs!(currentMemeber, ManyToMany)[0].mappedBy ~ "\"";
239                     } else {
240                         str ~= "\n bool manyToMany = false ;";
241                     }
242 
243                     str ~= "\n" ~ memberName ~ ` = lazyLoadList!(` ~ memType.stringof.replace("[]","") ~ 
244                                 `)(getLazyData("`~memberName~`"), manyToMany, `~mappedBy~`);`;
245                 } else {
246                     str ~= "\n" ~ memberName~` = lazyLoadSingle!(`~memType.stringof~`)(getLazyData("`~memberName~`"));`;
247                 }
248 
249                 str ~= `
250                     return `~memberName~`;
251                 }`;
252             }
253         }
254     }}
255 
256     // Try to load the other members which is not loaed in current mapping.
257     
258     str ~= "\n";
259     str ~= `
260     void loadLazyMembers() {
261         version(HUNT_ENTITY_DEBUG) {
262             infof("Try to load data for all the other object members in %s", typeid(` ~ T.stringof ~ `));
263         }
264         ` ~ allGetMethods ~ `
265     }
266     `;
267 
268     return str;
269 }  
270