<commit_before>// Copyright Naked Objects Group Ltd, 45 Station Road, Henley on Thames, UK, RG9 1AT
// Licensed 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.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.Serialization;
using System.Security.Cryptography;
using System.Text;
using Common.Logging;
using NakedObjects.Facade;
using NakedObjects.Facade.Translation;
using NakedObjects.Rest.Snapshot.Constants;
using NakedObjects.Rest.Snapshot.Utility;

namespace NakedObjects.Rest.Snapshot.Representations {
    [DataContract]
    public class Representation : IRepresentation {
        private static readonly object ModuleBuilderLock = new Object();
        protected CacheType Caching;
        protected string Etag;
        protected List<string> Warnings = new List<string>();
        private static readonly ILog Log = LogManager.GetLogger<Representation>();

        public Representation(IOidStrategy oidStrategy, RestControlFlags flags) {
            OidStrategy = oidStrategy;
            Flags = flags;
        }

        protected IOidStrategy OidStrategy { get; set; }
        protected RestControlFlags Flags { get; }

        private static ModuleBuilder ModuleBuilder { get; set; }

        protected RelType SelfRelType { get; set; }

        #region IRepresentation Members

        public virtual MediaTypeHeaderValue GetContentType() {
            return SelfRelType != null ? SelfRelType.GetMediaType(Flags) : null;
        }

        public EntityTagHeaderValue GetEtag() {
            return Etag != null ? new EntityTagHeaderValue($"\"{Etag}\"") : null;
        }

        public CacheType GetCaching() {
            return Caching;
        }

        public string[] GetWarnings() {
            var allWarnings = new List<string>(Warnings);

            try {
                PropertyInfo[] properties = GetType().GetProperties();

                IEnumerable<IRepresentation> repProperties = properties.Where(p => typeof(IRepresentation).IsAssignableFrom(p.PropertyType)).Select(p => (IRepresentation) p.GetValue(this, null));
                IEnumerable<IRepresentation> repProperties1 = properties.Where(p => typeof(IRepresentation[]).IsAssignableFrom(p.PropertyType)).SelectMany(p => (IRepresentation[]) p.GetValue(this, null));

                allWarnings.AddRange(repProperties.Where(p => p != null).SelectMany(p => p.GetWarnings()));
                allWarnings.AddRange(repProperties1.Where(p => p != null).SelectMany(p => p.GetWarnings()));
            }
            catch (Exception e) {
                Log.ErrorFormat("Error on getting warnings - is a Representation null?", e);
                throw;
            }

            return allWarnings.ToArray();
        }

        public virtual HttpResponseMessage GetAsMessage(MediaTypeFormatter formatter, Tuple<int, int, int> cacheSettings) {
            MediaTypeHeaderValue ct = GetContentType();

            if (ct != null) {
                formatter.SupportedMediaTypes.Add(ct);
            }

            var content = new ObjectContent<Representation>(this, formatter);
            var msg = new HttpResponseMessage {Content = content};
            msg.Content.Headers.ContentType = ct;

            SetCaching(msg, cacheSettings);

            return msg;
        }

        public Uri GetLocation() {
            return SelfRelType != null ? SelfRelType.GetUri() : null;
        }

        #endregion

        protected void SetCaching(HttpResponseMessage m, Tuple<int, int, int> cacheSettings) {
            int cacheTime = 0;

            switch (GetCaching()) {
                case CacheType.Transactional:
                    cacheTime = cacheSettings.Item1;
                    break;
                case CacheType.UserInfo:
                    cacheTime = cacheSettings.Item2;
                    break;
                case CacheType.NonExpiring:
                    cacheTime = cacheSettings.Item3;
                    break;
            }

            if (cacheTime == 0) {
                m.Headers.CacheControl = new CacheControlHeaderValue {NoCache = true};
                m.Headers.Pragma.Add(new NameValueHeaderValue("no-cache"));
            }
            else {
                m.Headers.CacheControl = new CacheControlHeaderValue {MaxAge = new TimeSpan(0, 0, 0, cacheTime)};
            }

            DateTime now = DateTime.UtcNow;

            m.Headers.Date = new DateTimeOffset(now);
            m.Content.Headers.Expires = new DateTimeOffset(now).Add(new TimeSpan(0, 0, 0, cacheTime));
        }

        protected string GetPropertyValueForEtag(IAssociationFacade property, IObjectFacade target) {
            IObjectFacade valueNakedObject = property.GetValue(target);
           
            if (valueNakedObject == null) {
                return "";
            }

            if (property.Specification.IsParseable) {
                return valueNakedObject.Object.ToString();
            }

            return OidStrategy.FrameworkFacade.OidTranslator.GetOidTranslation(target).Encode();
        }


        protected string GetTransientEtag(IObjectFacade target) {
            //var allProperties = target.Specification.Properties.Where(p => !p.IsCollection && !p.IsInline);
            //var unusableProperties = allProperties.Where(p => p.IsUsable(target).IsVetoed && !p.IsVisible(target));

            //var propertyValues = unusableProperties.ToDictionary(p => p.Id, p => GetPropertyValueForEtag(p, target));

            //return propertyValues.Aggregate("", (s, kvp) => s + kvp.Key + ":" + kvp.Value);
            return "";
        }

        protected void SetEtag(IObjectFacade target) {
            if (target.IsTransient) {
                Etag = GetTransientEtag(target);
            }
            else if (!target.Specification.IsService && !target.Specification.IsImmutable(target)) {
                string digest = target.Version.Digest;
                if (digest != null) {
                    Etag = digest;
                }
            }
        }

        private static void EnsureModuleBuilderExists() {
            if (ModuleBuilder == null) {
                const string assemblyName = "NakedObjectsRestProxies";
                AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName(assemblyName), AssemblyBuilderAccess.Run);
                ModuleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName);
            }
        }

        private static void CreateProperty(TypeBuilder typeBuilder, Type propertyType, string name) {
            FieldBuilder fieldBuilder = typeBuilder.DefineField("_" + name, propertyType, FieldAttributes.Private);

            PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(name, PropertyAttributes.None, propertyType, Type.EmptyTypes);

            MethodBuilder getMethodBuilder = typeBuilder.DefineMethod("get_" + name, MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.Public, propertyType, Type.EmptyTypes);<commit_msg>start transient etag work<commit_after>// Copyright Naked Objects Group Ltd, 45 Station Road, Henley on Thames, UK, RG9 1AT
// Licensed 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.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.Serialization;
using System.Security.Cryptography;
using System.Text;
using Common.Logging;
using NakedObjects.Facade;
using NakedObjects.Facade.Translation;
using NakedObjects.Rest.Snapshot.Constants;
using NakedObjects.Rest.Snapshot.Utility;

namespace NakedObjects.Rest.Snapshot.Representations {
    [DataContract]
    public class Representation : IRepresentation {
        private static readonly object ModuleBuilderLock = new Object();
        protected CacheType Caching;
        protected string Etag;
        protected List<string> Warnings = new List<string>();
        private static readonly ILog Log = LogManager.GetLogger<Representation>();

        public Representation(IOidStrategy oidStrategy, RestControlFlags flags) {
            OidStrategy = oidStrategy;
            Flags = flags;
        }

        protected IOidStrategy OidStrategy { get; set; }
        protected RestControlFlags Flags { get; }

        private static ModuleBuilder ModuleBuilder { get; set; }

        protected RelType SelfRelType { get; set; }

        #region IRepresentation Members

        public virtual MediaTypeHeaderValue GetContentType() {
            return SelfRelType != null ? SelfRelType.GetMediaType(Flags) : null;
        }

        public EntityTagHeaderValue GetEtag() {
            return Etag != null ? new EntityTagHeaderValue($"\"{Etag}\"") : null;
        }

        public CacheType GetCaching() {
            return Caching;
        }

        public string[] GetWarnings() {
            var allWarnings = new List<string>(Warnings);

            try {
                PropertyInfo[] properties = GetType().GetProperties();

                IEnumerable<IRepresentation> repProperties = properties.Where(p => typeof(IRepresentation).IsAssignableFrom(p.PropertyType)).Select(p => (IRepresentation) p.GetValue(this, null));
                IEnumerable<IRepresentation> repProperties1 = properties.Where(p => typeof(IRepresentation[]).IsAssignableFrom(p.PropertyType)).SelectMany(p => (IRepresentation[]) p.GetValue(this, null));

                allWarnings.AddRange(repProperties.Where(p => p != null).SelectMany(p => p.GetWarnings()));
                allWarnings.AddRange(repProperties1.Where(p => p != null).SelectMany(p => p.GetWarnings()));
            }
            catch (Exception e) {
                Log.ErrorFormat("Error on getting warnings - is a Representation null?", e);
                throw;
            }

            return allWarnings.ToArray();
        }

        public virtual HttpResponseMessage GetAsMessage(MediaTypeFormatter formatter, Tuple<int, int, int> cacheSettings) {
            MediaTypeHeaderValue ct = GetContentType();

            if (ct != null) {
                formatter.SupportedMediaTypes.Add(ct);
            }

            var content = new ObjectContent<Representation>(this, formatter);
            var msg = new HttpResponseMessage {Content = content};
            msg.Content.Headers.ContentType = ct;

            SetCaching(msg, cacheSettings);

            return msg;
        }

        public Uri GetLocation() {
            return SelfRelType != null ? SelfRelType.GetUri() : null;
        }

        #endregion

        protected void SetCaching(HttpResponseMessage m, Tuple<int, int, int> cacheSettings) {
            int cacheTime = 0;

            switch (GetCaching()) {
                case CacheType.Transactional:
                    cacheTime = cacheSettings.Item1;
                    break;
                case CacheType.UserInfo:
                    cacheTime = cacheSettings.Item2;
                    break;
                case CacheType.NonExpiring:
                    cacheTime = cacheSettings.Item3;
                    break;
            }

            if (cacheTime == 0) {
                m.Headers.CacheControl = new CacheControlHeaderValue {NoCache = true};
                m.Headers.Pragma.Add(new NameValueHeaderValue("no-cache"));
            }
            else {
                m.Headers.CacheControl = new CacheControlHeaderValue {MaxAge = new TimeSpan(0, 0, 0, cacheTime)};
            }

            DateTime now = DateTime.UtcNow;

            m.Headers.Date = new DateTimeOffset(now);
            m.Content.Headers.Expires = new DateTimeOffset(now).Add(new TimeSpan(0, 0, 0, cacheTime));
        }

        protected string GetPropertyValueForEtag(IAssociationFacade property, IObjectFacade target) {
            IObjectFacade valueNakedObject = property.GetValue(target);
           
            if (valueNakedObject == null) {
                return "";
            }

            if (property.Specification.IsParseable) {
                return valueNakedObject.Object.ToString();
            }

            return OidStrategy.FrameworkFacade.OidTranslator.GetOidTranslation(target).Encode();
        }


        protected string GetTransientEtag(IObjectFacade target) {
            var allProperties = target.Specification.Properties.Where(p => !p.IsCollection && !p.IsInline);
            var unusableProperties = allProperties.Where(p => p.IsUsable(target).IsVetoed && !p.IsVisible(target));

            var propertyValues = unusableProperties.ToDictionary(p => p.Id, p => GetPropertyValueForEtag(p, target));

            return propertyValues.Aggregate("", (s, kvp) => s + kvp.Key + ":" + kvp.Value);
        }

        protected void SetEtag(IObjectFacade target) {
            if (target.IsTransient) {
                Etag = GetTransientEtag(target);
            }
            else if (!target.Specification.IsService && !target.Specification.IsImmutable(target)) {
                string digest = target.Version.Digest;
                if (digest != null) {
                    Etag = digest;
                }
            }
        }

        private static void EnsureModuleBuilderExists() {
            if (ModuleBuilder == null) {
                const string assemblyName = "NakedObjectsRestProxies";
                AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName(assemblyName), AssemblyBuilderAccess.Run);
                ModuleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName);
            }
        }

        private static void CreateProperty(TypeBuilder typeBuilder, Type propertyType, string name) {
            FieldBuilder fieldBuilder = typeBuilder.DefineField("_" + name, propertyType, FieldAttributes.Private);

            PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(name, PropertyAttributes.None, propertyType, Type.EmptyTypes);

            MethodBuilder getMethodBuilder = typeBuilder.DefineMethod("get_" + name, MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.Public, propertyType, Type.EmptyTypes);
{'fraction_non_alphanumeric': 0.09090909090909091, 'fraction_numerical': 0.0027331294332010154, 'mean_word_length': 2.4380313199105146, 'pattern_counts': {'":': 2, '<': 21, '<?xml version=': 0, '>': 47, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '15071', 'n_tokens_mistral': 3918, 'n_tokens_neox': 3764, 'n_words': 961}